From 58dc015e01f9ff50ba4bd6364160577c8df11178 Mon Sep 17 00:00:00 2001 From: waner Date: Tue, 19 May 2026 17:12:53 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fix:=E6=9D=83=E9=99=90=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 15 +- config/application.yml.example | 1 + docs/deployment/tms-deployment.md | 322 +++++++++++++++++- scripts/cisd_init_flow.sh | 4 +- scripts/pcie_realcard_smoke.sh | 2 +- scripts/resource-restore/replay-mq.sh | 36 +- scripts/resource-restore/restore-db.sh | 3 +- .../standard-init/root-sbin/tms-rabbitmqctl | 92 +++++ .../init/config/InitExecutorProperties.java | 4 +- .../ConfigurableInitStepExecutor.java | 52 ++- src/main/resources/application.yml | 13 +- ...ApplyResourceBackupScriptContractTest.java | 44 +++ .../restore/MqReplayScriptContractTest.java | 41 +++ .../ConfigurableInitStepExecutorTest.java | 61 +++- 14 files changed, 651 insertions(+), 39 deletions(-) create mode 100755 scripts/standard-init/root-sbin/tms-rabbitmqctl diff --git a/README.md b/README.md index f4dbea4..fbc6d1c 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ mvn spring-boot:run 完整部署手册: - [docs/deployment/tms-deployment.md](/Users/waner/Work/CISD/文档/tms-framework/docs/deployment/tms-deployment.md) - [docs/deployment/tms-init-admin-permission-checklist.md](/Users/waner/Work/CISD/文档/tms-framework/docs/deployment/tms-init-admin-permission-checklist.md) +- [docs/deployment/tms-root-admin-actions.md](/Users/waner/Work/CISD/文档/tms-framework/docs/deployment/tms-root-admin-actions.md) 1. 构建 jar 包: @@ -39,6 +40,7 @@ cp scripts/tms.sh /home/tms/scripts/tms.sh cp scripts/standard-init/*.sh /home/tms/bin/ sudo cp scripts/standard-init/root-sbin/tms-run-standard-vendor-step /usr/local/sbin/tms-run-standard-vendor-step sudo cp scripts/standard-init/root-sbin/tms-prepare-standard-permissions /usr/local/sbin/tms-prepare-standard-permissions +sudo cp scripts/standard-init/root-sbin/tms-rabbitmqctl /usr/local/sbin/tms-rabbitmqctl cp scripts/resource-restore/*.sh /home/tms/bin/resource-restore/ cp config/application.yml.example /home/tms/config/application.yml chmod +x /home/tms/bin/*.sh @@ -48,6 +50,8 @@ sudo chown root:root /usr/local/sbin/tms-run-standard-vendor-step sudo chmod 755 /usr/local/sbin/tms-run-standard-vendor-step sudo chown root:root /usr/local/sbin/tms-prepare-standard-permissions sudo chmod 755 /usr/local/sbin/tms-prepare-standard-permissions +sudo chown root:root /usr/local/sbin/tms-rabbitmqctl +sudo chmod 755 /usr/local/sbin/tms-rabbitmqctl ``` 3. 启动 / 停止 / 状态: @@ -67,6 +71,7 @@ sudo chmod 755 /usr/local/sbin/tms-prepare-standard-permissions - 标准 CISD 初始化辅助脚本位于 `scripts/standard-init/`,部署时需复制到 `/home/tms/bin/`。 - `scripts/standard-init/root-sbin/tms-run-standard-vendor-step` 是标准版 vendor 客户化脚本的 root 受控入口,部署到 `/usr/local/sbin/` 后通过 sudoers 授权给 `tms` 使用。 - `scripts/standard-init/root-sbin/tms-prepare-standard-permissions` 是标准版介质权限预处理脚本,由 `root` 在初始化前手工执行,用于修正 `/home/cmep4i/cpackage`、license、SQL load、CMSP/CMTP 目录权限。 +- `scripts/standard-init/root-sbin/tms-rabbitmqctl` 是 RabbitMQ CLI 的 root 受控入口,初始化和资源恢复默认通过 `sudo -n /usr/local/sbin/tms-rabbitmqctl` 执行,避免 `tms` 用户 Erlang cookie 与 RabbitMQ 服务端不一致。 - 资源恢复辅助脚本位于 `scripts/resource-restore/`,部署时需复制到 `/home/tms/bin/resource-restore/`。 - `apply_standard_db.sh` 依赖预置环境变量,例如 `DB_USER`、`DB_PASSWORD`,不要直接写入 `application.yml`。 - 运行目录结构、配置项说明、文件上传 `fileId` 流程和故障排查,请查看上面的完整部署手册。 @@ -99,8 +104,8 @@ java -jar tms-framework.jar --tms.crypto.decrypt - 本地构建和测试统一使用 JDK 17,与项目和 CI 运行时保持一致。 Open: -- Swagger UI: http://localhost:8080/swagger-ui.html -- Internal API docs JSON: http://localhost:8080/v3/api-docs/internal-api +- Swagger UI: http://localhost:8088/swagger-ui.html +- Internal API docs JSON: http://localhost:8088/v3/api-docs/internal-api - Runtime-generated Swagger/OpenAPI docs are the source of truth for `/api/**`. - Internal health API: `GET /api/v1/device/status` - Internal sign preview API: `POST /api/v1/sign/preview` @@ -208,8 +213,8 @@ Open: - `/home/tms/bin/stop_standard_rabbitmq.sh` - `/home/tms/bin/apply_standard_db.sh` - `tms.init.executor.standard-db-username/password` 已正确配置 -- `tms.init.executor.rabbitmq-ctl-command` 指向可执行的 `rabbitmqctl` -- 运行时 Swagger 文档以 `http://:8080/swagger-ui.html` 和 `http://:8080/v3/api-docs/internal-api` 为准 +- `tms.init.executor.rabbitmq-ctl-command` 默认 `sudo -n /usr/local/sbin/tms-rabbitmqctl` +- 运行时 Swagger 文档以 `http://:8088/swagger-ui.html` 和 `http://:8088/v3/api-docs/internal-api` 为准 ## Offline Upgrade @@ -563,7 +568,7 @@ mvn -q test Example: ```bash -export BASE_URL=http://127.0.0.1:8080 +export BASE_URL=http://127.0.0.1:8088 export INTERNAL_TOKEN=change-me-internal-token export ALG_SM3=1 export ALG_SM4_ECB=1025 diff --git a/config/application.yml.example b/config/application.yml.example index e0a38ec..f27791b 100644 --- a/config/application.yml.example +++ b/config/application.yml.example @@ -68,6 +68,7 @@ tms: standard-rabbit-stop-command: /home/tms/bin/stop_standard_rabbitmq.sh standard-post-check-command: /home/tms/bin/check_standard_runtime.sh standard-vendor-step-runner-command: /usr/local/sbin/tms-run-standard-vendor-step + rabbitmq-ctl-command: sudo -n /usr/local/sbin/tms-rabbitmqctl upload-base-dir: /home/tms/uploads staging-root-dir: /tmp/tms-init-staging direct-receiver-license-target-path: /home/cmep4d/cmep/license/cmep.license diff --git a/docs/deployment/tms-deployment.md b/docs/deployment/tms-deployment.md index 1d39b51..6b7c6e6 100644 --- a/docs/deployment/tms-deployment.md +++ b/docs/deployment/tms-deployment.md @@ -364,12 +364,12 @@ java -version ### 3.3 网络与端口 TMS 默认监听端口: -- `8080` +- `8088` 检查命令: ```bash -ss -lntp | grep 8080 +ss -lntp | grep 8088 ``` ### 3.4 建议具备的系统命令 @@ -398,12 +398,33 @@ TMS 统一部署到 `/home/tms`: │ ├── apply_standard_db.sh │ ├── start_standard_apps.sh │ ├── start_standard_nginx.sh -│ └── check_standard_runtime.sh +│ ├── stop_standard_apps.sh +│ ├── stop_standard_nginx.sh +│ ├── stop_standard_rabbitmq.sh +│ ├── check_standard_runtime.sh +│ └── resource-restore +│ ├── apply-resource-backup.sh +│ ├── restore-db.sh +│ └── replay-mq.sh ├── config │ └── application.yml +├── gateway +│ ├── gm +│ │ ├── start.sh +│ │ ├── nginx.conf +│ │ └── config/http/tms.conf +│ ├── tls13 +│ │ ├── start.sh +│ │ ├── nginx.conf +│ │ └── conf.d/tms.conf +│ └── logs ├── logs │ ├── tms-framework.out.log │ └── tms-framework.err.log +├── root-sbin +│ ├── tms-prepare-standard-permissions +│ ├── tms-rabbitmqctl +│ └── tms-run-standard-vendor-step ├── run │ └── tms-framework.pid ├── scripts @@ -418,7 +439,9 @@ TMS 统一部署到 `/home/tms`: 目录用途: - `/home/tms/bin`:初始化后置辅助脚本目录,由 `tms.init.executor.*command` 引用 - `/home/tms/config`:Spring Boot 外部配置目录 +- `/home/tms/gateway`:TMS 独立网关配置、启动脚本和日志目录 - `/home/tms/logs`:TMS 运行日志 +- `/home/tms/root-sbin`:root 受控脚本的部署包暂存目录,实际执行位置为 `/usr/local/sbin` - `/home/tms/run`:PID 文件目录 - `/home/tms/scripts`:TMS 启停控制脚本目录 - `/home/tms/tmp`:初始化执行日志和临时目录 @@ -432,10 +455,30 @@ TMS 统一部署到 `/home/tms`: | `/home/tms/scripts/tms.sh` | `scripts/tms.sh` | | `/home/tms/bin/apply_standard_db.sh` | `scripts/standard-init/apply_standard_db.sh` | | `/home/tms/bin/start_standard_apps.sh` | `scripts/standard-init/start_standard_apps.sh` | +| `/home/tms/bin/stop_standard_apps.sh` | `scripts/standard-init/stop_standard_apps.sh` | | `/home/tms/bin/start_standard_nginx.sh` | `scripts/standard-init/start_standard_nginx.sh` | +| `/home/tms/bin/stop_standard_nginx.sh` | `scripts/standard-init/stop_standard_nginx.sh` | +| `/home/tms/bin/stop_standard_rabbitmq.sh` | `scripts/standard-init/stop_standard_rabbitmq.sh` | | `/home/tms/bin/check_standard_runtime.sh` | `scripts/standard-init/check_standard_runtime.sh` | +| `/home/tms/bin/resource-restore/apply-resource-backup.sh` | `scripts/resource-restore/apply-resource-backup.sh` | +| `/home/tms/bin/resource-restore/restore-db.sh` | `scripts/resource-restore/restore-db.sh` | +| `/home/tms/bin/resource-restore/replay-mq.sh` | `scripts/resource-restore/replay-mq.sh` | +| `/home/tms/root-sbin/tms-run-standard-vendor-step` | `scripts/standard-init/root-sbin/tms-run-standard-vendor-step` | +| `/home/tms/root-sbin/tms-prepare-standard-permissions` | `scripts/standard-init/root-sbin/tms-prepare-standard-permissions` | +| `/home/tms/root-sbin/tms-rabbitmqctl` | `scripts/standard-init/root-sbin/tms-rabbitmqctl` | +| `/usr/local/sbin/tms-run-standard-vendor-step` | 由 `/home/tms/root-sbin/tms-run-standard-vendor-step` 复制安装 | +| `/usr/local/sbin/tms-prepare-standard-permissions` | 由 `/home/tms/root-sbin/tms-prepare-standard-permissions` 复制安装 | +| `/usr/local/sbin/tms-rabbitmqctl` | 由 `/home/tms/root-sbin/tms-rabbitmqctl` 复制安装 | +| `/home/tms/gateway/tls13/start.sh` | `deployment/tms-gateway/tls13/start.sh` | +| `/home/tms/gateway/tls13/nginx.conf` | `deployment/tms-gateway/tls13/nginx.conf` | +| `/home/tms/gateway/tls13/conf.d/tms.conf` | `deployment/tms-gateway/tls13/conf.d/tms.conf` | +| `/home/tms/gateway/gm/start.sh` | `deployment/tms-gateway/gm/start.sh` | +| `/home/tms/gateway/gm/nginx.conf` | `deployment/tms-gateway/gm/nginx.conf` | +| `/home/tms/gateway/gm/config/http/tms.conf` | `deployment/tms-gateway/gm/config/http/tms.conf` | | `/home/tms/config/application.yml` | 由 `config/application.yml.example` 复制生成 | +`scripts/cisd_init_precheck.sh` 和 `scripts/pcie_realcard_smoke.sh` 属于诊断脚本,不作为 TMS 常驻运行脚本默认部署。现场需要时可复制到 `/home/tms/tools/` 临时执行。 + ## 6. 部署步骤 ### 6.1 构建 jar 包 @@ -448,7 +491,9 @@ mvn -q -DskipTests package ### 6.2 创建运行目录 ```bash -sudo mkdir -p /home/tms/{bin,config,scripts,logs,run,tmp,uploads,libs} +sudo mkdir -p /home/tms/{bin,config,scripts,logs,run,tmp,uploads,libs,root-sbin,tools} +sudo mkdir -p /home/tms/bin/resource-restore +sudo mkdir -p /home/tms/gateway/{tls13,gm,logs/tls13,logs/gm} sudo chown -R "$(whoami)":"$(whoami)" /home/tms ``` @@ -458,9 +503,16 @@ sudo chown -R "$(whoami)":"$(whoami)" /home/tms cp target/tms-framework-*.jar /home/tms/tms-framework.jar cp scripts/tms.sh /home/tms/scripts/tms.sh cp scripts/standard-init/*.sh /home/tms/bin/ +cp scripts/resource-restore/*.sh /home/tms/bin/resource-restore/ +cp scripts/standard-init/root-sbin/* /home/tms/root-sbin/ +cp -R deployment/tms-gateway/tls13/* /home/tms/gateway/tls13/ +cp -R deployment/tms-gateway/gm/* /home/tms/gateway/gm/ cp config/application.yml.example /home/tms/config/application.yml chmod +x /home/tms/scripts/tms.sh chmod +x /home/tms/bin/*.sh +chmod +x /home/tms/bin/resource-restore/*.sh +chmod +x /home/tms/gateway/tls13/start.sh +chmod +x /home/tms/gateway/gm/start.sh ``` ### 6.4 检查部署结果 @@ -516,7 +568,7 @@ tms: ### 7.3 标准版初始化关键配置 标准版初始化重点配置项: -- `standard-home-dir`:默认 `/home/cemp4i` +- `standard-home-dir`:默认 `/home/cmep4i` - `standard-cpconfig-path`:标准版 `cpconfig.cfg` 路径 - `standard-db-script-base-dir`:默认 `${standard-home-dir}/cpackage` - `standard-db-load-dir`:默认 `${standard-home-dir}/mysql/loadfilepath` @@ -524,6 +576,8 @@ tms: - `standard-web-front-zip-path`:默认 `${standard-home-dir}/cpackage/front/front.zip` - `standard-web-target-dir`:默认 `/usr/local/nginx/html` - `standard-web-organization-json-path`:默认 `/usr/local/nginx/html/organization.json` +- `standard-vendor-step-runner-command`:默认 `/usr/local/sbin/tms-run-standard-vendor-step`,用于 `SET_RABBITMQ`、`SET_TLQ`、`TLQ_CONF_APPLY`、`DEPLOY_RECEIVER` +- `rabbitmq-ctl-command`:默认 `sudo -n /usr/local/sbin/tms-rabbitmqctl`,初始化和资源恢复统一通过 root 受控 wrapper 调用 RabbitMQ CLI ### 7.4 不建议直接写入 application.yml 的凭据 @@ -559,7 +613,7 @@ tms: - `DB_HOST`,默认 `127.0.0.1` - `DB_PORT`,默认 `4000` - `DB_NAME`,默认 `CMEP` -- `LOAD_DIR`,默认 `/home/cemp4i/mysql/loadfilepath` +- `LOAD_DIR`,默认 `/home/cmep4i/mysql/loadfilepath` 示例: @@ -579,8 +633,8 @@ export DB_PASSWORD='secret' - 启动 CMTP 依赖: -- `/home/cemp4i/cmsp/start_cmsp.sh` -- `/home/cemp4i/cmtp/start_cmtp.sh` +- `/home/cmep4i/cmsp/start_cmsp.sh` +- `/home/cmep4i/cmtp/start_cmtp.sh` - 用户 `cmep4i` ### 8.3 `start_standard_nginx.sh` @@ -608,6 +662,50 @@ export DB_PASSWORD='secret' 该脚本仅用于初始化后的结果校验,不负责修复。 +### 8.5 `tms-run-standard-vendor-step` + +来源: +- [tms-run-standard-vendor-step](/Users/waner/Work/CISD/文档/tms-framework/scripts/standard-init/root-sbin/tms-run-standard-vendor-step) + +部署位置: +- `/usr/local/sbin/tms-run-standard-vendor-step` + +作用: +- 作为 root 拥有的受控入口,按固定参数执行标准 vendor 脚本 +- 内部切换到 `cmep4i` 用户后执行 `/home/cmep4i/cpackage/toolsh/*.sh` +- 当前只允许 `setfraq`、`setftq`、`settlq`、`setsptp` + +部署命令: + +```bash +sudo cp scripts/standard-init/root-sbin/tms-run-standard-vendor-step /usr/local/sbin/tms-run-standard-vendor-step +sudo chown root:root /usr/local/sbin/tms-run-standard-vendor-step +sudo chmod 755 /usr/local/sbin/tms-run-standard-vendor-step +``` + +sudoers 建议: + +```sudoers +Defaults:tms !requiretty + +tms ALL=(root) NOPASSWD: \ + /usr/local/sbin/tms-rabbitmqctl *, \ + /usr/local/sbin/tms-run-standard-vendor-step setfraq, \ + /usr/local/sbin/tms-run-standard-vendor-step setftq, \ + /usr/local/sbin/tms-run-standard-vendor-step settlq, \ + /usr/local/sbin/tms-run-standard-vendor-step setsptp +``` + +验证: + +```bash +su - tms +sudo -n /usr/local/sbin/tms-run-standard-vendor-step setsptp +ls -l /home/cmep4i/cmsp /home/cmep4i/cmtp +``` + +执行后 `/home/cmep4i` 下由 vendor 脚本生成或替换的文件应保持为 `cmep4i` 可访问,不应再由 `tms` 直接写成 `tms:tms`。 + ## 9. 文件上传与 `fileId` 模型 ### 9.1 核心原则 @@ -683,6 +781,207 @@ tail -n 100 /home/tms/logs/tms-framework.out.log tail -n 100 /home/tms/logs/tms-framework.err.log ``` +## 10.6 TMS 独立网关(可选) + +当现场需要为 `tms-framework` 单独提供 HTTPS 入口时,建议新增独立 TMS 网关,而不是接管标准收发器现有 `/usr/local/nginx`。 + +这套独立网关的定位是: + +- 只服务 TMS 前端和 TMS API +- 独立于标准收发器现有 Nginx +- 不参与 `START_NGINX`、`STOP_NGINX`、`POST_CHECK` + +标准收发器现有 Nginx 仍然保持: + +- 配置路径:`/usr/local/nginx/conf/nginx.conf` +- Web 发布目录:`/usr/local/nginx/html` +- 默认标准 Web 验收入口:`8000` + +### 10.6.1 推荐端口 + +推荐保留如下边界: + +- 标准收发器现有 Web:`8000` +- TMS TLS 1.3 网关:`443` +- TMS 国密网关:`8443` +- TMS 后端:`127.0.0.1:8088` + +因此在启用独立网关前,建议先检查: + +```bash +ss -lntp | grep -E ':443|:8443|:8000|:8088' +``` + +### 10.6.2 推荐目录 + +建议按如下结构部署: + +```text +/home/tms +├── web +│ └── dist +└── gateway + ├── tls13 + ├── gm + └── logs + ├── tls13 + └── gm +``` + +说明: + +- `/home/tms/web/dist`:TMS 前端构建产物 +- `/home/tms/gateway/tls13`:TLS 1.3 网关配置 +- `/home/tms/gateway/gm`:国密网关配置 +- `/home/tms/gateway/logs/*`:对应网关日志 + +### 10.6.3 仓库模板位置 + +仓库已提供两套模板: + +- TLS 1.3: + - `deployment/tms-gateway/tls13/nginx.conf` + - `deployment/tms-gateway/tls13/conf.d/tms.conf` + - `deployment/tms-gateway/tls13/start.sh` + - `deployment/tms-gateway/tls13/README.md` +- 国密: + - `deployment/tms-gateway/gm/nginx.conf` + - `deployment/tms-gateway/gm/config/http/tms.conf` + - `deployment/tms-gateway/gm/start.sh` + - `deployment/tms-gateway/gm/README.md` + +部署时建议将模板复制到: + +- `/home/tms/gateway/tls13` +- `/home/tms/gateway/gm` + +### 10.6.4 前端发布目录 + +TMS 前端建议统一发布到: + +```text +/home/tms/web/dist +``` + +不建议继续沿用其他项目使用过的 `/app/hsm/vue` 命名。 + +网关容器建议统一将: + +```text +/home/tms/web +``` + +挂载到容器: + +```text +/app +``` + +这样容器内静态资源根目录仍保持: + +```text +/app/dist +``` + +### 10.6.5 后端代理约定 + +独立网关统一代理到: + +```text +http://127.0.0.1:8088 +``` + +需要放行的核心路由: + +- `/` +- `/api/v1/` +- `/swagger-ui.html` +- `/swagger-ui/` +- `/v3/api-docs/` + +特别注意: + +- TMS 接口本身挂在 `/api/v1/**` +- 网关 `proxy_pass` 不应把 `/api/v1/` 前缀错误改写掉 +- 若网关误把 `/api/v1/device/status` 转成 `/device/status`,后端会返回 404 + +因此后续修改网关配置时,应保持“原始请求路径透传到 TMS 后端”这一约束。 + +### 10.6.6 证书与镜像 + +TLS 1.3 网关: + +- 默认使用 `nginx:latest` +- 证书目录:`/home/tms/gateway/tls13/certs` +- 默认文件名: + - `rsa.dev.pem` + - `rsa.dev.key` + +国密网关: + +- 默认使用 `gateway.lib:latest` +- 证书目录:`/home/tms/gateway/gm/certs` +- 默认文件名: + - `sm2.sig.pem` + - `sm2.sig.key` + - `sm2.enc.pem` + - `sm2.enc.key` + - `rsa.dev.pem` + - `rsa.dev.key` + +补充说明: + +- TLS 1.3 网关默认读取自己的 `/home/tms/gateway/tls13/certs` +- 国密网关默认读取自己的 `/home/tms/gateway/gm/certs` +- 如果现场希望 TLS 1.3 直接复用国密目录中的 RSA 证书,可在启动 TLS 1.3 网关时额外指定 `CERT_DIR=/home/tms/gateway/gm/certs` + +如果现场为离线部署,可参考: + +```bash +docker load -i /Users/waner/Work/CISD/文档/manager/tls13/tls13.tar +docker load -i /Users/waner/Work/CISD/文档/manager/gateway.tar +``` + +### 10.6.7 启动与验收 + +TLS 1.3 网关: + +```bash +cd /home/tms/gateway/tls13 +bash start.sh +``` + +国密网关: + +```bash +cd /home/tms/gateway/gm +bash start.sh +``` + +TLS 1.3 验收可先检查: + +```bash +curl -k https://127.0.0.1/ +curl -k https://127.0.0.1/swagger-ui.html +curl -k https://127.0.0.1/api/v1/device/status +``` + +国密入口建议使用现场指定的国密兼容浏览器或客户端进行验证。 + +### 10.6.8 与标准收发器 Nginx 的边界 + +本独立网关方案不改变以下既有边界: + +- 不写入 `/usr/local/nginx/conf/nginx.conf` +- 不使用 `/usr/local/nginx/html` +- 不替代标准收发器 Web 发布流程 +- 不改变标准收发器初始化和 reset 当前逻辑 + +换句话说: + +- 标准收发器 Nginx 继续服务标准产品自身的 Web +- TMS 独立网关只负责 TMS 自己的前端和 API + ## 11. 初始化前置检查 预检查脚本: @@ -859,8 +1158,8 @@ pgrep -f '/usr/local/nginx/sbin/nginx' ```bash /home/tms/scripts/tms.sh status -ss -lntp | grep 8080 -curl -s http://127.0.0.1:8080/api/v1/device/status +ss -lntp | grep 8088 +curl -s http://127.0.0.1:8088/api/v1/device/status find /home/tms/uploads -maxdepth 2 -type f | sort ls -l /home/tms/bin ls -l /home/tms/config/application.yml @@ -871,7 +1170,10 @@ ls -l /home/tms/config/application.yml ```bash bash -n /home/tms/bin/apply_standard_db.sh bash -n /home/tms/bin/start_standard_apps.sh +bash -n /home/tms/bin/stop_standard_apps.sh bash -n /home/tms/bin/start_standard_nginx.sh +bash -n /home/tms/bin/stop_standard_nginx.sh +bash -n /home/tms/bin/stop_standard_rabbitmq.sh bash -n /home/tms/bin/check_standard_runtime.sh ``` diff --git a/scripts/cisd_init_flow.sh b/scripts/cisd_init_flow.sh index 46408a8..57fd8a2 100755 --- a/scripts/cisd_init_flow.sh +++ b/scripts/cisd_init_flow.sh @@ -9,12 +9,12 @@ set -euo pipefail # REQUEST_JSON=/tmp/cisd-init-standard-rabbit.json ./scripts/cisd_init_flow.sh # # Optional env: -# BASE_URL=http://127.0.0.1:8080 +# BASE_URL=http://127.0.0.1:8088 # INTERNAL_TOKEN=change-me-internal-token # OUT_DIR=/tmp/cisd-init-run # FETCH_FAILED_STEP_LOG=true -BASE_URL="${BASE_URL:-http://127.0.0.1:8080}" +BASE_URL="${BASE_URL:-http://127.0.0.1:8088}" INTERNAL_TOKEN="${INTERNAL_TOKEN:-change-me-internal-token}" REQUEST_JSON="${REQUEST_JSON:-}" OUT_DIR="${OUT_DIR:-/tmp/cisd-init-run}" diff --git a/scripts/pcie_realcard_smoke.sh b/scripts/pcie_realcard_smoke.sh index ac02e08..1c0cf19 100755 --- a/scripts/pcie_realcard_smoke.sh +++ b/scripts/pcie_realcard_smoke.sh @@ -6,7 +6,7 @@ set -euo pipefail # chmod +x scripts/pcie_realcard_smoke.sh # ./scripts/pcie_realcard_smoke.sh -BASE_URL="${BASE_URL:-http://127.0.0.1:8080}" +BASE_URL="${BASE_URL:-http://127.0.0.1:8088}" INTERNAL_TOKEN="${INTERNAL_TOKEN:-change-me-internal-token}" TIMEOUT_SECONDS="${TIMEOUT_SECONDS:-15}" diff --git a/scripts/resource-restore/replay-mq.sh b/scripts/resource-restore/replay-mq.sh index 083c7a3..c4dd8c1 100755 --- a/scripts/resource-restore/replay-mq.sh +++ b/scripts/resource-restore/replay-mq.sh @@ -40,25 +40,44 @@ json_text() { sed -nE 's/.*"'"${key}"'"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p' "${CONTEXT_FILE}" | head -n 1 } +split_command() { + local value="$1" + # shellcheck disable=SC2206 + CTL_COMMAND=(${value}) +} + find_ctl() { local configured="${RABBITMQ_CTL_COMMAND:-}" if [[ -n "${configured}" ]]; then - printf '%s' "${configured}" + split_command "${configured}" + return 0 + fi + if [[ -x "/usr/local/sbin/tms-rabbitmqctl" ]] && command -v sudo >/dev/null 2>&1; then + CTL_COMMAND=(sudo -n /usr/local/sbin/tms-rabbitmqctl) return 0 fi for candidate in rabbitmqctl /usr/local/rabbitmq/sbin/rabbitmqctl /usr/local/rabbitmq-server/sbin/rabbitmqctl /usr/sbin/rabbitmqctl /usr/lib/rabbitmq/bin/rabbitmqctl; do if command -v "${candidate}" >/dev/null 2>&1; then - command -v "${candidate}" + CTL_COMMAND=("$(command -v "${candidate}")") return 0 fi if [[ -x "${candidate}" ]]; then - printf '%s' "${candidate}" + CTL_COMMAND=("${candidate}") return 0 fi done return 1 } +run_ctl() { + local timeout_seconds="${RABBITMQ_CTL_TIMEOUT_SECONDS:-30}" + if command -v timeout >/dev/null 2>&1; then + timeout "${timeout_seconds}" "${CTL_COMMAND[@]}" "$@" + else + "${CTL_COMMAND[@]}" "$@" + fi +} + if [[ -n "${MQ_REPLAY_COMMAND:-}" ]]; then log "delegating mq replay to configured command" "${MQ_REPLAY_COMMAND}" --context-file "${CONTEXT_FILE}" --work-dir "${WORK_DIR}" >> "${LOG_FILE}" 2>&1 @@ -85,15 +104,16 @@ if [[ -z "${USER_NAME}" || -z "${USER_PASS}" ]]; then exit 1 fi -CTL="$(find_ctl)" || { +CTL_COMMAND=() +find_ctl || { echo "[ERROR] rabbitmqctl not found" >&2 exit 1 } -"${CTL}" status >> "${LOG_FILE}" 2>&1 || log "rabbitmqctl status failed, continue replay" -"${CTL}" add_vhost "${VHOST}" >> "${LOG_FILE}" 2>&1 || true -"${CTL}" add_user "${USER_NAME}" "${USER_PASS}" >> "${LOG_FILE}" 2>&1 || "${CTL}" change_password "${USER_NAME}" "${USER_PASS}" >> "${LOG_FILE}" 2>&1 -"${CTL}" set_permissions -p "${VHOST}" "${USER_NAME}" ".*" ".*" ".*" >> "${LOG_FILE}" 2>&1 +run_ctl status >> "${LOG_FILE}" 2>&1 || log "rabbitmqctl status failed, continue replay" +run_ctl add_vhost "${VHOST}" >> "${LOG_FILE}" 2>&1 || true +run_ctl add_user "${USER_NAME}" "${USER_PASS}" >> "${LOG_FILE}" 2>&1 || run_ctl change_password "${USER_NAME}" "${USER_PASS}" >> "${LOG_FILE}" 2>&1 +run_ctl set_permissions -p "${VHOST}" "${USER_NAME}" ".*" ".*" ".*" >> "${LOG_FILE}" 2>&1 log "rabbitmq user and permissions replayed" if [[ -n "${RABBIT_QUEUE_REPLAY_SCRIPT_PATH:-}" && -x "${RABBIT_QUEUE_REPLAY_SCRIPT_PATH}" ]]; then diff --git a/scripts/resource-restore/restore-db.sh b/scripts/resource-restore/restore-db.sh index 4c7a9ed..8ed8450 100755 --- a/scripts/resource-restore/restore-db.sh +++ b/scripts/resource-restore/restore-db.sh @@ -60,5 +60,4 @@ fi --host="${HOST}" \ --port="${PORT}" \ --user="${USERNAME}" \ - --password="${PASSWORD}" \ - "${DB_NAME}" < "${SQL_FILE}" + --password="${PASSWORD}" < "${SQL_FILE}" diff --git a/scripts/standard-init/root-sbin/tms-rabbitmqctl b/scripts/standard-init/root-sbin/tms-rabbitmqctl new file mode 100755 index 0000000..29bfad5 --- /dev/null +++ b/scripts/standard-init/root-sbin/tms-rabbitmqctl @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ "$(id -u)" -ne 0 ]]; then + echo "tms-rabbitmqctl must be executed as root through sudo" >&2 + exit 77 +fi + +if [[ $# -lt 1 ]]; then + echo "usage: tms-rabbitmqctl [args...]" >&2 + exit 64 +fi + +require_arg_count() { + local expected="$1" + shift + if [[ $# -ne "${expected}" ]]; then + echo "unsupported rabbitmqctl arguments" >&2 + exit 64 + fi +} + +case "$1" in + status|list_users) + require_arg_count 1 "$@" + ;; + add_vhost|delete_user) + require_arg_count 2 "$@" + ;; + add_user|change_password|authenticate_user) + require_arg_count 3 "$@" + ;; + set_permissions) + if [[ $# -ne 7 || "${2:-}" != "-p" ]]; then + echo "unsupported rabbitmqctl set_permissions arguments" >&2 + exit 64 + fi + ;; + clear_permissions) + if [[ $# -ne 4 || "${2:-}" != "-p" ]]; then + echo "unsupported rabbitmqctl clear_permissions arguments" >&2 + exit 64 + fi + ;; + list_queues) + if [[ ! ( $# -eq 2 || ( $# -eq 4 && "${2:-}" == "-p" ) ) ]]; then + echo "unsupported rabbitmqctl list_queues arguments" >&2 + exit 64 + fi + ;; + delete_queue) + if [[ ! ( $# -eq 2 || ( $# -eq 4 && "${2:-}" == "-p" ) ) ]]; then + echo "unsupported rabbitmqctl delete_queue arguments" >&2 + exit 64 + fi + ;; + *) + echo "unsupported rabbitmqctl command: $1" >&2 + exit 64 + ;; +esac + +export ERLANG_HOME="${ERLANG_HOME:-/usr/local/erlang}" +export RABBITMQ_HOME="${RABBITMQ_HOME:-/usr/local/rabbitmq}" +export PATH="${ERLANG_HOME}/bin:${RABBITMQ_HOME}/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin" + +if [[ -n "${RABBITMQ_CTL:-}" ]]; then + RABBITMQ_CTL_CANDIDATES=("${RABBITMQ_CTL}") +else + RABBITMQ_CTL_CANDIDATES=( + "${RABBITMQ_HOME}/sbin/rabbitmqctl" + "/usr/local/rabbitmq/sbin/rabbitmqctl" + "/usr/local/rabbitmq-server/sbin/rabbitmqctl" + "/usr/sbin/rabbitmqctl" + "/usr/lib/rabbitmq/bin/rabbitmqctl" + ) +fi + +RABBITMQ_CTL="" +for candidate in "${RABBITMQ_CTL_CANDIDATES[@]}"; do + if [[ -x "${candidate}" ]]; then + RABBITMQ_CTL="${candidate}" + break + fi +done + +if [[ ! -x "${RABBITMQ_CTL}" ]]; then + echo "rabbitmqctl not executable" >&2 + exit 69 +fi + +exec "${RABBITMQ_CTL}" "$@" diff --git a/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java b/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java index e5f736f..6b6c580 100644 --- a/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java +++ b/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java @@ -15,6 +15,8 @@ public class InitExecutorProperties { private static final String DEFAULT_STANDARD_RUN_USER = "cmep4i"; private static final String DEFAULT_STANDARD_VENDOR_STEP_RUNNER_COMMAND = "/usr/local/sbin/tms-run-standard-vendor-step"; + private static final String DEFAULT_RABBITMQ_CTL_COMMAND = + "sudo -n /usr/local/sbin/tms-rabbitmqctl"; private Mode mode = Mode.SIMULATE; private int timeoutSec = 600; @@ -104,7 +106,7 @@ public class InitExecutorProperties { private String rabbitAdminUsername = "admin"; private String rabbitAdminPassword = "admin"; // RabbitMQ 管理命令路径 - private String rabbitmqCtlCommand = "rabbitmqctl"; + private String rabbitmqCtlCommand = DEFAULT_RABBITMQ_CTL_COMMAND; private String rabbitmqAdminCommand = "/usr/sbin/rabbitmqadmin"; // 标准版 RabbitMQ 初始化脚本(创建admin/通道账号、替换队列脚本机构号) private String standardRabbitSetupScriptPath = ""; diff --git a/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java b/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java index f1e918e..a929acd 100644 --- a/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java +++ b/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java @@ -684,6 +684,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { private String resolveRabbitmqCtlCommand() { List candidates = new ArrayList<>(); candidates.add(firstNonBlank(properties.getRabbitmqCtlCommand(), "rabbitmqctl")); + candidates.add("sudo -n /usr/local/sbin/tms-rabbitmqctl"); candidates.add("/usr/local/rabbitmq/sbin/rabbitmqctl"); candidates.add("/usr/local/rabbitmq-server/sbin/rabbitmqctl"); candidates.add("/usr/sbin/rabbitmqctl"); @@ -694,6 +695,12 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { if (isBlank(candidate)) { continue; } + if (candidateHasArguments(candidate)) { + if (isRunnableCommandPrefix(candidate)) { + return candidate; + } + continue; + } if (candidate.contains("/")) { try { Path path = Paths.get(candidate).normalize().toAbsolutePath(); @@ -716,6 +723,37 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { ); } + private boolean isRunnableCommandPrefix(String command) { + List parts = splitCommandPrefix(command); + if (parts.isEmpty()) { + return false; + } + String executable = parts.get(0); + if (executable.contains("/")) { + try { + Path path = Paths.get(executable).normalize().toAbsolutePath(); + return Files.exists(path) && Files.isRegularFile(path); + } catch (InvalidPathException ex) { + return false; + } + } + return !isBlank(findExecutableInPath(executable)); + } + + private boolean candidateHasArguments(String command) { + return splitCommandPrefix(command).size() > 1; + } + + private List splitCommandPrefix(String command) { + String value = normalize(command); + if (isBlank(value)) { + return List.of(); + } + return Arrays.stream(value.split("\\s+")) + .filter(part -> !isBlank(part)) + .toList(); + } + private String findExecutableInPath(String commandName) { String pathValue = System.getenv("PATH"); if (isBlank(pathValue)) { @@ -1920,7 +1958,19 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { } private int runRabbitmqCommand(Path logFile, List command, boolean ignoreFailure) throws IOException, InterruptedException { - return runCommand(logFile, command, ignoreFailure, null, buildRabbitmqEnvironment()); + return runCommand(logFile, expandRabbitmqCommand(command), ignoreFailure, null, buildRabbitmqEnvironment()); + } + + private List expandRabbitmqCommand(List command) { + if (command == null || command.isEmpty()) { + return List.of(); + } + List expanded = new ArrayList<>(splitCommandPrefix(command.get(0))); + if (expanded.isEmpty()) { + expanded.add(command.get(0)); + } + expanded.addAll(command.subList(1, command.size())); + return expanded; } private int runCommand(Path logFile, List command, boolean ignoreFailure) throws IOException, InterruptedException { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index aec6974..377f673 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -13,7 +13,7 @@ spring: server: # TMS HTTP 服务监听端口。 - port: 8080 + port: 8088 management: endpoints: @@ -135,9 +135,8 @@ tms: rabbit-admin-username: ${TMS_INIT_EXECUTOR_RABBIT_ADMIN_USERNAME:admin} # RabbitMQ 管理员密码(预制)。 rabbit-admin-password: ${TMS_INIT_EXECUTOR_RABBIT_ADMIN_PASSWORD:admin} - # RabbitMQ 控制命令(用于新增用户、授权等)。 - # 建议线上配置为绝对路径,例如 /usr/local/rabbitmq/sbin/rabbitmqctl。 - rabbitmq-ctl-command: ${TMS_INIT_EXECUTOR_RABBITMQ_CTL_COMMAND:/usr/local/rabbitmq/sbin/rabbitmqctl} + # RabbitMQ 控制命令(用于新增用户、授权等);默认通过 root 受控 wrapper 执行,避免 tms 与 RabbitMQ 服务端 Erlang cookie 不一致。 + rabbitmq-ctl-command: ${TMS_INIT_EXECUTOR_RABBITMQ_CTL_COMMAND:sudo -n /usr/local/sbin/tms-rabbitmqctl} # RabbitMQ Admin CLI 命令路径(用于创建队列等)。 rabbitmq-admin-command: ${TMS_INIT_EXECUTOR_RABBITMQ_ADMIN_COMMAND:/usr/sbin/rabbitmqadmin} # 标准版 RabbitMQ 初始化脚本路径(保留兼容;MQ_USER_APPLY 当前优先直接调用 rabbitmqctl/rabbitmqadmin)。 @@ -322,7 +321,7 @@ tms: # 备份/恢复标准收发器业务库名;第一版默认使用 CMEP。 cmep-database-name: ${TMS_BACKUP_CMEP_DATABASE_NAME:CMEP} # 恢复完成后的最小健康检查地址。 - health-check-url: ${TMS_BACKUP_HEALTH_CHECK_URL:http://127.0.0.1:8080/actuator/health} + health-check-url: ${TMS_BACKUP_HEALTH_CHECK_URL:http://127.0.0.1:8088/actuator/health} # 健康检查请求超时(秒)。 health-check-timeout-seconds: ${TMS_BACKUP_HEALTH_CHECK_TIMEOUT_SECONDS:5} # 健康检查最大等待时间(秒);TMS 启动命令返回后,Spring Boot 仍可能需要一段时间才监听端口。 @@ -340,7 +339,7 @@ tms: internal-token: ${TMS_INTERNAL_TOKEN:change-me-internal-token} internal-auth: # 是否启用 /api/** 的内部登录鉴权;开发联调时可临时关闭。 - enabled: ${TMS_SECURITY_INTERNAL_AUTH_ENABLED:false} + enabled: ${TMS_SECURITY_INTERNAL_AUTH_ENABLED:true} # 关闭内部鉴权后,注入请求上下文的调试角色。 debug-role-code: ${TMS_SECURITY_INTERNAL_AUTH_DEBUG_ROLE_CODE:OPS_ADMIN} # 关闭内部鉴权后,注入请求上下文的调试认证等级。 @@ -349,7 +348,7 @@ tms: debug-session-token: ${TMS_SECURITY_INTERNAL_AUTH_DEBUG_SESSION_TOKEN:DEBUG-BYPASS} replay: # 是否启用防重放校验;本地 Postman/联调可临时设为 false,生产环境应保持 true。 - enabled: ${TMS_SECURITY_REPLAY_ENABLED:false} + enabled: ${TMS_SECURITY_REPLAY_ENABLED:true} openapi: # 外部签名服务接口允许的时间戳偏差(秒),防重放。 timestamp-skew-seconds: 300 diff --git a/src/test/java/com/cisd/tms/modules/backup/restore/ApplyResourceBackupScriptContractTest.java b/src/test/java/com/cisd/tms/modules/backup/restore/ApplyResourceBackupScriptContractTest.java index 6efb660..f1d2ec8 100644 --- a/src/test/java/com/cisd/tms/modules/backup/restore/ApplyResourceBackupScriptContractTest.java +++ b/src/test/java/com/cisd/tms/modules/backup/restore/ApplyResourceBackupScriptContractTest.java @@ -332,6 +332,33 @@ class ApplyResourceBackupScriptContractTest { Assertions.assertTrue(status.contains("database restore failed")); } + @Test + void shouldExecuteDatabaseSqlWithoutConnectingToTargetDatabaseFirst() throws Exception { + Path commandLog = tempDir.resolve("mysql-restore.log"); + Path mysql = tempDir.resolve("mysql"); + Files.writeString(mysql, """ + #!/usr/bin/env bash + for arg in "$@"; do + printf 'ARG:%%s\\n' "$arg" >> "%s" + done + input="$(cat)" + if [ -n "$input" ]; then + printf 'STDIN:%%s\\n' "$input" >> "%s" + fi + """.formatted(commandLog, commandLog)); + mysql.toFile().setExecutable(true); + Path sqlFile = tempDir.resolve("CMEP.sql"); + Files.writeString(sqlFile, "CREATE DATABASE IF NOT EXISTS `CMEP`;\nUSE `CMEP`;\nselect 1;"); + + int exitCode = runDbRestoreScriptForExit(mysql, "CMEP", sqlFile); + + Assertions.assertEquals(0, exitCode); + String log = Files.readString(commandLog); + Assertions.assertTrue(log.contains("ARG:--host=127.0.0.1")); + Assertions.assertFalse(log.contains("ARG:CMEP" + System.lineSeparator())); + Assertions.assertTrue(log.contains("STDIN:CREATE DATABASE IF NOT EXISTS `CMEP`;")); + } + private static void runScript(Path workDir, Map environment) throws Exception { int exitCode = runScriptForExit(workDir, environment); Assertions.assertEquals(0, exitCode); @@ -350,6 +377,23 @@ class ApplyResourceBackupScriptContractTest { return process.waitFor(); } + private static int runDbRestoreScriptForExit(Path mysqlPath, String dbName, Path sqlFile) throws Exception { + ProcessBuilder builder = new ProcessBuilder( + "bash", + Path.of("scripts/resource-restore/restore-db.sh").toAbsolutePath().normalize().toString(), + "--mysql-path", mysqlPath.toString(), + "--host", "127.0.0.1", + "--port", "4000", + "--username", "root", + "--password", "secret", + "--db-name", dbName, + "--sql-file", sqlFile.toString() + ); + builder.directory(Path.of(".").toAbsolutePath().normalize().toFile()); + Process process = builder.start(); + return process.waitFor(); + } + private Path fakeScript(String name, Path commandLog) throws Exception { Path script = tempDir.resolve(name); Files.writeString(script, """ diff --git a/src/test/java/com/cisd/tms/modules/backup/restore/MqReplayScriptContractTest.java b/src/test/java/com/cisd/tms/modules/backup/restore/MqReplayScriptContractTest.java index 2726bca..c257264 100644 --- a/src/test/java/com/cisd/tms/modules/backup/restore/MqReplayScriptContractTest.java +++ b/src/test/java/com/cisd/tms/modules/backup/restore/MqReplayScriptContractTest.java @@ -39,6 +39,36 @@ class MqReplayScriptContractTest { Assertions.assertTrue(log.contains("set_permissions -p /RQ tms .* .* .*")); } + @Test + void shouldReplayRabbitMqThroughPrefixedRootWrapperCommand() throws Exception { + Path context = tempDir.resolve("mq-restore-context.json"); + Files.writeString(context, """ + {"schemaVersion":"1.0","productType":"ENTERPRISE","mqType":"RABBITMQ","orgCode":"BKCHCNBJXXX", + "mq":{"channelUsername":"bank_user","channelPassword":"Secret123!"}} + """); + Path commandLog = tempDir.resolve("commands.log"); + Path sudo = fakeSudo(commandLog); + Path wrapper = tempDir.resolve("tms-rabbitmqctl"); + + ProcessBuilder builder = new ProcessBuilder( + "bash", + Path.of("scripts/resource-restore/replay-mq.sh").toAbsolutePath().normalize().toString(), + "--context-file", + context.toString(), + "--work-dir", + tempDir.resolve("work").toString() + ); + builder.environment().put("RABBITMQ_CTL_COMMAND", sudo + " -n " + wrapper); + Process process = builder.start(); + + Assertions.assertEquals(0, process.waitFor(), new String(process.getErrorStream().readAllBytes())); + String log = Files.readString(commandLog); + Assertions.assertTrue(log.contains("-n " + wrapper + " status")); + Assertions.assertTrue(log.contains("-n " + wrapper + " add_vhost /RQ")); + Assertions.assertTrue(log.contains("-n " + wrapper + " add_user bank_user Secret123!")); + Assertions.assertTrue(log.contains("-n " + wrapper + " set_permissions -p /RQ bank_user .* .* .*")); + } + @Test void shouldDelegateToCustomReplayCommandWhenConfigured() throws Exception { Path context = tempDir.resolve("mq-restore-context.json"); @@ -71,4 +101,15 @@ class MqReplayScriptContractTest { script.toFile().setExecutable(true); return script; } + + private Path fakeSudo(Path commandLog) throws Exception { + Path script = tempDir.resolve("sudo"); + Files.writeString(script, """ + #!/usr/bin/env bash + printf '%%s\\n' "$*" >> "%s" + exit 0 + """.formatted(commandLog)); + script.toFile().setExecutable(true); + return script; + } } diff --git a/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java b/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java index ffeb133..3050f34 100644 --- a/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java +++ b/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java @@ -1112,13 +1112,15 @@ class ConfigurableInitStepExecutorTest { Assertions.assertTrue(exampleContent.contains("standard-nginx-start-command: /home/tms/bin/start_standard_nginx.sh")); Assertions.assertTrue(exampleContent.contains("standard-nginx-stop-command: /home/tms/bin/stop_standard_nginx.sh")); Assertions.assertTrue(exampleContent.contains("standard-post-check-command: /home/tms/bin/check_standard_runtime.sh")); + Assertions.assertTrue(exampleContent.contains("rabbitmq-ctl-command: sudo -n /usr/local/sbin/tms-rabbitmqctl")); String applicationContent = Files.readString(applicationConfig); Assertions.assertTrue(applicationContent.contains("standard-app-start-command: ${TMS_INIT_EXECUTOR_STANDARD_APP_START_COMMAND:/home/tms/bin/start_standard_apps.sh}")); Assertions.assertTrue(applicationContent.contains("standard-app-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_APP_STOP_COMMAND:/home/tms/bin/stop_standard_apps.sh}")); - Assertions.assertTrue(applicationContent.contains("standard-nginx-start-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_START_COMMAND:/home/tms/bin/start_standard_nginx.sh}")); - Assertions.assertTrue(applicationContent.contains("standard-nginx-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_STOP_COMMAND:/home/tms/bin/stop_standard_nginx.sh}")); + Assertions.assertTrue(applicationContent.contains("standard-nginx-start-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_START_COMMAND:sudo -n /usr/local/sbin/tms-start-standard-nginx}")); + Assertions.assertTrue(applicationContent.contains("standard-nginx-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_STOP_COMMAND:sudo -n /usr/local/sbin/tms-stop-standard-nginx}")); Assertions.assertTrue(applicationContent.contains("standard-post-check-command: ${TMS_INIT_EXECUTOR_STANDARD_POST_CHECK_COMMAND:/home/tms/bin/check_standard_runtime.sh}")); + Assertions.assertTrue(applicationContent.contains("rabbitmq-ctl-command: ${TMS_INIT_EXECUTOR_RABBITMQ_CTL_COMMAND:sudo -n /usr/local/sbin/tms-rabbitmqctl}")); String readmeContent = Files.readString(readme); Assertions.assertTrue(readmeContent.contains("scripts/standard-init/")); @@ -1247,6 +1249,61 @@ class ConfigurableInitStepExecutorTest { Assertions.assertTrue(callsContent.contains("set_permissions -p /RQ bank_user .* .* .*")); } + @Test + void shouldApplyStandardRabbitUserThroughPrefixedRootWrapperCommand() throws IOException { + Path tempRoot = Files.createTempDirectory("init-executor-rabbit-wrapper-test"); + Path cpconfig = tempRoot.resolve("cpconfig.cfg"); + Path calls = tempRoot.resolve("calls.log"); + Path sudo = tempRoot.resolve("sudo"); + Path wrapper = tempRoot.resolve("tms-rabbitmqctl"); + Files.writeString(cpconfig, "MY_CIPSID_OR_BIC=AAAABBBBXXX\n"); + Files.writeString( + sudo, + "#!/bin/bash\n" + + "echo \"$*\" >> \"" + calls + "\"\n" + + "exit 0\n" + ); + Assertions.assertTrue(sudo.toFile().setExecutable(true)); + + InitExecutorProperties properties = new InitExecutorProperties(); + properties.setMode(InitExecutorProperties.Mode.LOCAL); + properties.setLogDir(tempRoot.resolve("logs").toString()); + properties.setStagingRootDir(tempRoot.resolve("staging").toString()); + properties.setStandardCpconfigPath(cpconfig.toString()); + properties.setRabbitmqCtlCommand(sudo + " -n " + wrapper); + ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper()); + + InitTaskEntity task = new InitTaskEntity(); + task.setTaskId("TASK-RABBIT-WRAPPER-1"); + task.setProductType("ENTERPRISE"); + task.setInitPlanJson(buildInitPlanJson( + "ENTERPRISE", + Map.of( + "orgCodeType", "BIC", + "orgCode", "AAAABBBBXXX", + "orgNameCn", "测试机构", + "deployMode", "SINGLE", + "nodes", Map.of("node01Ip", "10.0.0.1"), + "mq", Map.of("mqType", "RABBITMQ", "channelUsername", "bank_user", "channelPassword", "bank_pass"), + "licenses", Map.of("receiverLicenseFileId", "receiver-001") + ) + )); + + InitTaskStepEntity step = new InitTaskStepEntity(); + step.setStepNo(1); + step.setStepCode("MQ_USER_APPLY"); + step.setCommandLine("internal::mq_user_apply"); + + InitStepExecutionResult result = executor.execute(task, step); + + Assertions.assertTrue(result.isSuccess()); + String callsContent = Files.readString(calls); + Assertions.assertTrue(callsContent.contains("-n " + wrapper + " status")); + Assertions.assertTrue(callsContent.contains("-n " + wrapper + " add_vhost /RQ")); + Assertions.assertTrue(callsContent.contains("-n " + wrapper + " add_user bank_user bank_pass")); + Assertions.assertTrue(callsContent.contains("-n " + wrapper + " set_permissions -p /RQ bank_user .* .* .*")); + } + @Test void shouldApplyStandardRabbitUserForIndirectRabbitmqTlq() throws IOException { Path tempRoot = Files.createTempDirectory("init-executor-rabbit-setup-indirect-tlq-test"); From 99b3a8ce598b3d491e73f69271fc7980ebce1b1a Mon Sep 17 00:00:00 2001 From: xydkj Date: Wed, 20 May 2026 09:36:18 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E6=A0=87=E5=87=86=E6=94=B6=E5=8F=91?= =?UTF-8?q?=E5=99=A8=E5=AE=A2=E6=88=B7=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openapi-sdk/pom.xml | 66 ++ .../cisd/openapi/OpenapiSdkApplication.java | 15 + .../java/com/cisd/openapi/SignClient.java | 468 ++++++++++ .../com/cisd/openapi/config/OkHttpConfig.java | 20 + .../openapi/config/SignServerProperties.java | 29 + .../com/cisd/openapi/dto/CertificateInfo.java | 10 + .../openapi/dto/DettachedSignRequest.java | 19 + .../openapi/dto/DettachedSignResponse.java | 9 + .../openapi/dto/DettachedVerifyRequest.java | 20 + .../openapi/dto/DettachedVerifyResponse.java | 9 + .../dto/DettachedVerifySimpleRequest.java | 19 + .../dto/DettachedVerifySimpleResponse.java | 9 + .../dto/DeviceActivateInfoResponse.java | 19 + .../openapi/dto/DeviceActivateRequest.java | 11 + .../cisd/openapi/dto/ErrorDataResponse.java | 9 + .../cisd/openapi/dto/GenericCertificate.java | 123 +++ .../openapi/dto/LastReturnCodeRequest.java | 12 + .../openapi/dto/LastReturnCodeResponse.java | 8 + .../com/cisd/openapi/dto/RawSignRequest.java | 19 + .../com/cisd/openapi/dto/RawSignResponse.java | 8 + .../cisd/openapi/dto/RawVerifyRequest.java | 24 + .../cisd/openapi/dto/RawVerifyResponse.java | 10 + .../cisd/openapi/dto/UploadCertRequest.java | 18 + .../cisd/openapi/dto/UploadCertResponse.java | 9 + .../openapi/exception/BusinessException.java | 16 + .../java/com/cisd/openapi/util/Base64.java | 324 +++++++ .../cisd/openapi/util/DNItemReverseUtil.java | 117 +++ .../cisd/openapi/util/NetSignAgentRes.java | 629 ++++++++++++++ .../cisd/openapi/util/NetSignAgentUtil.java | 819 ++++++++++++++++++ .../java/com/cisd/openapi/util/PBCUtil.java | 27 + .../cisd/openapi/util/X509NameTokenizer.java | 68 ++ .../src/main/resources/application.yml | 13 + .../java/com/cisd/openapi/SignClientTest.java | 90 ++ .../src/test/resources/application-test.yml | 10 + 34 files changed, 3076 insertions(+) create mode 100644 openapi-sdk/pom.xml create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/OpenapiSdkApplication.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/SignClient.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/config/OkHttpConfig.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/config/SignServerProperties.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/CertificateInfo.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedSignRequest.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedSignResponse.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifyRequest.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifyResponse.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifySimpleRequest.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifySimpleResponse.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/DeviceActivateInfoResponse.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/DeviceActivateRequest.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/ErrorDataResponse.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/GenericCertificate.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/LastReturnCodeRequest.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/LastReturnCodeResponse.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/RawSignRequest.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/RawSignResponse.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/RawVerifyRequest.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/RawVerifyResponse.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/UploadCertRequest.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/dto/UploadCertResponse.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/exception/BusinessException.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/util/Base64.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/util/DNItemReverseUtil.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/util/NetSignAgentRes.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/util/NetSignAgentUtil.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/util/PBCUtil.java create mode 100644 openapi-sdk/src/main/java/com/cisd/openapi/util/X509NameTokenizer.java create mode 100644 openapi-sdk/src/main/resources/application.yml create mode 100644 openapi-sdk/src/test/java/com/cisd/openapi/SignClientTest.java create mode 100644 openapi-sdk/src/test/resources/application-test.yml diff --git a/openapi-sdk/pom.xml b/openapi-sdk/pom.xml new file mode 100644 index 0000000..2fc0055 --- /dev/null +++ b/openapi-sdk/pom.xml @@ -0,0 +1,66 @@ + + 4.0.0 + + com.cisd + openapi-sdk + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 3.5.11 + + + + 17 + 17 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.projectlombok + lombok + provided + + + + + com.squareup.okhttp3 + okhttp + 4.12.0 + + + + org.bouncycastle + bcprov-jdk18on + 1.83 + compile + + + org.springframework.boot + spring-boot-test + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + io.swagger.core.v3 + swagger-annotations-jakarta + 2.2.28 + compile + + + + + diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/OpenapiSdkApplication.java b/openapi-sdk/src/main/java/com/cisd/openapi/OpenapiSdkApplication.java new file mode 100644 index 0000000..f23015e --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/OpenapiSdkApplication.java @@ -0,0 +1,15 @@ +package com.cisd.openapi; + + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + +@SpringBootApplication +@ConfigurationPropertiesScan +public class OpenapiSdkApplication +{ + public static void main( String[] args ) { + SpringApplication.run(OpenapiSdkApplication.class, args); + } +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/SignClient.java b/openapi-sdk/src/main/java/com/cisd/openapi/SignClient.java new file mode 100644 index 0000000..423320f --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/SignClient.java @@ -0,0 +1,468 @@ +package com.cisd.openapi; + +import com.cisd.openapi.config.SignServerProperties; +import com.cisd.openapi.dto.*; +import com.cisd.openapi.exception.BusinessException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.*; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.Base64; +import java.util.UUID; + +@Component +public class SignClient { + + private static final MediaType JSON_MEDIA_TYPE = + MediaType.parse("application/json; charset=utf-8"); + + private final OkHttpClient okHttpClient; + private final ObjectMapper objectMapper; + private final SignServerProperties properties; + + private final ThreadLocal lastReturnCode = new ThreadLocal<>(); + + public SignClient(OkHttpClient okHttpClient, + ObjectMapper objectMapper, + SignServerProperties properties) { + this.okHttpClient = okHttpClient; + this.objectMapper = objectMapper; + this.properties = properties; + } + + + public String rawSign(byte[] origBytes, String dn) { + + String sessionId = UUID.randomUUID().toString().replace("-", ""); + + if (origBytes == null) { + lastReturnCode.set(-100100); + throw new IllegalArgumentException("origBytes不能为空"); + } + + if (dn == null || dn.isBlank()) { + lastReturnCode.set(-100100); + throw new IllegalArgumentException("dn不能为空"); + } + + if (origBytes.length > 4096){ + lastReturnCode.set(-100100); + throw new IllegalArgumentException("数据过大"); + } + + String origBase64 = Base64.getEncoder().encodeToString(origBytes); + RawSignRequest rawSignRequest = new RawSignRequest( + origBase64, + dn + ); + + rawSignRequest.setSessionId(sessionId); + + String requestJson; + try { + requestJson = objectMapper.writeValueAsString(rawSignRequest); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("serialize failed", e); + } + + RequestBody requestBody = RequestBody.create( + requestJson, + JSON_MEDIA_TYPE + ); + + Request request = new Request.Builder() + .url(buildUrl(properties.getRawSignPath())) + .post(requestBody) + .addHeader("Content-Type", "application/json") + .addHeader("X-API-Key", properties.getApiKey()) + .build(); + + + try (Response response = okHttpClient.newCall(request).execute()) { + String responseBody = response.body() == null ? "" : response.body().string(); + JsonNode root = objectMapper.readTree(responseBody); + int code = root.get("code").asInt(); + System.out.println(root.toPrettyString()); + if (code == 200){ + RawSignResponse data = objectMapper.treeToValue(root.get("data"), RawSignResponse.class); + return data.getSignature(); + } else if (code == 400){ + ErrorDataResponse errorData = objectMapper.treeToValue(root.get("data"), ErrorDataResponse.class); + lastReturnCode.set(errorData.getCode()); + throw new BusinessException("参数错误或签名失败(具体见返回码)", errorData); + } else if (code == 401){ + throw new RuntimeException("未认证或会话无效"); + } else{ + throw new RuntimeException("会话不存在"); + } + + } catch (IOException e) { + throw new RuntimeException("调用rawSign异常", e); + } + } + + + public boolean rawVerify(byte[] origBytes, String certStr, String dn) { + + String sessionId = UUID.randomUUID().toString().replace("-", ""); + + if (origBytes == null) { + lastReturnCode.set(-100100); + throw new IllegalArgumentException("origBytes不能为空"); + } + + if (dn == null || dn.isBlank()) { + lastReturnCode.set(-100100); + throw new IllegalArgumentException("dn不能为空"); + } + + if (certStr == null || certStr.isBlank()) { + lastReturnCode.set(-100100); + throw new IllegalArgumentException("certStr签名串不能为空"); + } + + if (origBytes.length > 4096) { + lastReturnCode.set(-100100); + throw new IllegalArgumentException("数据过大"); + } + + String origBase64 = Base64.getEncoder().encodeToString(origBytes); +// String origBase64 = "SGVsbG8gV29ybGQ="; + RawVerifyRequest rawVerifyRequest = new RawVerifyRequest( + origBase64, + dn, + certStr + ); + rawVerifyRequest.setSessionId(sessionId); + + String requestJson; + try { + requestJson = objectMapper.writeValueAsString(rawVerifyRequest); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("serialize failed", e); + } + + RequestBody requestBody = RequestBody.create( + requestJson, + JSON_MEDIA_TYPE + ); + + Request request = new Request.Builder() + .url(buildUrl(properties.getRawVerifyPath())) + .post(requestBody) + .addHeader("Content-Type", "application/json") + .addHeader("X-API-Key", properties.getApiKey()) + .build(); + + + try (Response response = okHttpClient.newCall(request).execute()) { + String responseBody = response.body() == null ? "" : response.body().string(); + JsonNode root = objectMapper.readTree(responseBody); + int code = root.get("code").asInt(); + if (code == 200) { + RawVerifyResponse data = objectMapper.treeToValue(root.get("data"), RawVerifyResponse.class); + return data.getVerified(); + } else if (code == 400) { + ErrorDataResponse errorData = objectMapper.treeToValue(root.get("data"), ErrorDataResponse.class); + lastReturnCode.set(errorData.getCode()); + throw new BusinessException("参数错误或签名失败(具体见返回码)", errorData); + } else if (code == 401) { + throw new RuntimeException("未认证或会话无效"); + } else { + throw new RuntimeException("会话不存在"); + } + + } catch (IOException e) { + throw new RuntimeException("调用rawVerify异常", e); + } + } + + + public String dettachedSign(byte[] origBytes, String dn) { + + String sessionId = UUID.randomUUID().toString().replace("-", ""); + + if (origBytes == null) { + throw new IllegalArgumentException("origBytes不能为空"); + } + + if (dn == null || dn.isBlank()) { + throw new IllegalArgumentException("dn不能为空"); + } + + if (origBytes.length > 4096){ + throw new IllegalArgumentException("数据过大大"); + } + + String origBase64 = Base64.getEncoder().encodeToString(origBytes); + DettachedSignRequest dettachedSignRequest = new DettachedSignRequest( + sessionId, + origBase64, + dn + ); + + String requestJson; + try { + requestJson = objectMapper.writeValueAsString(dettachedSignRequest); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("serialize failed", e); + } + + RequestBody requestBody = RequestBody.create( + requestJson, + JSON_MEDIA_TYPE + ); + + Request request = new Request.Builder() + .url(buildUrl(properties.getDettachedSignPath())) + .post(requestBody) + .addHeader("Content-Type", "application/json") + .addHeader("X-API-Key", properties.getApiKey()) + .build(); + + + try (Response response = okHttpClient.newCall(request).execute()) { + String responseBody = response.body() == null ? "" : response.body().string(); + JsonNode root = objectMapper.readTree(responseBody); + int code = root.get("code").asInt(); + + if (code == 200){ + DettachedSignResponse data = objectMapper.treeToValue(root.get("data"), DettachedSignResponse.class); + return data.getSignature(); + } else if (code == 400){ + ErrorDataResponse errorData = objectMapper.treeToValue(root.get("data"), ErrorDataResponse.class); + throw new BusinessException("参数错误或签名失败(具体见返回码)", errorData); + } else if (code == 401){ + throw new RuntimeException("未认证或会话无效"); + } else{ + throw new RuntimeException("会话不存在"); + } + + } catch (IOException e) { + throw new RuntimeException("调用dettachedSign异常", e); + } + } + + + public GenericCertificate dettachedVerify(byte[] origBytes, String certStr) { + + String sessionId = UUID.randomUUID().toString().replace("-", ""); + + if (origBytes == null) { + throw new IllegalArgumentException("origBytes不能为空"); + } + + if (certStr == null || certStr.isBlank()) { + throw new IllegalArgumentException("certStr签名串不能为空"); + } + + if (origBytes.length > 4096){ + throw new IllegalArgumentException("数据过大"); + } + + String origBase64 = Base64.getEncoder().encodeToString(origBytes); + DettachedVerifyRequest dettachedVerifyRequest = new DettachedVerifyRequest( + origBase64, + certStr + ); + dettachedVerifyRequest.setSessionId(sessionId); + + + String requestJson; + try { + requestJson = objectMapper.writeValueAsString(dettachedVerifyRequest); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("serialize failed", e); + } + + RequestBody requestBody = RequestBody.create( + requestJson, + JSON_MEDIA_TYPE + ); + + Request request = new Request.Builder() + .url(buildUrl(properties.getDettachedVerifyPath())) + .post(requestBody) + .addHeader("Content-Type", "application/json") + .addHeader("X-API-Key", properties.getApiKey()) + .build(); + + + try (Response response = okHttpClient.newCall(request).execute()) { + String responseBody = response.body() == null ? "" : response.body().string(); + JsonNode root = objectMapper.readTree(responseBody); + int code = root.get("code").asInt(); + System.out.println(root.toPrettyString()); + if (code == 200){ + DettachedVerifyResponse data = objectMapper.treeToValue(root.get("data"), DettachedVerifyResponse.class); + return data.getGenericCertificate(); + } else if (code == 400){ + ErrorDataResponse errorData = objectMapper.treeToValue(root.get("data"), ErrorDataResponse.class); +// System.out.println(errorData.getCode()); +// System.out.println(errorData.getMessage()); + throw new BusinessException("参数错误或签名失败(具体见返回码)", errorData); + } else if (code == 401){ + throw new RuntimeException("未认证或会话无效"); + } else{ + throw new RuntimeException("会话不存在"); + } + + } catch (IOException e) { + throw new RuntimeException("调用dettachedVerify异常", e); + } + } + + + + public GenericCertificate dettachedVerifySimple(byte[] origBytes, String certStr) { + + String sessionId = UUID.randomUUID().toString().replace("-", ""); + + if (origBytes == null) { + throw new IllegalArgumentException("origBytes不能为空"); + } + + if (certStr == null || certStr.isBlank()) { + throw new IllegalArgumentException("certStr签名串不能为空"); + } + + if (origBytes.length > 4096){ + throw new IllegalArgumentException("数据过大"); + } + + String origBase64 = Base64.getEncoder().encodeToString(origBytes); + DettachedVerifySimpleRequest dettachedVerifySimpleRequest = new DettachedVerifySimpleRequest( + origBase64, + certStr + ); + dettachedVerifySimpleRequest.setSessionId(sessionId); + + String requestJson; + try { + requestJson = objectMapper.writeValueAsString(dettachedVerifySimpleRequest); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("serialize failed", e); + } + + + RequestBody requestBody = RequestBody.create( + requestJson, + JSON_MEDIA_TYPE + ); + + Request request = new Request.Builder() + .url(buildUrl(properties.getDettachedVerifySimplePath())) + .post(requestBody) + .addHeader("Content-Type", "application/json") + .addHeader("X-API-Key", properties.getApiKey()) + .build(); + + + try (Response response = okHttpClient.newCall(request).execute()) { + String responseBody = response.body() == null ? "" : response.body().string(); + JsonNode root = objectMapper.readTree(responseBody); + int code = root.get("code").asInt(); + System.out.println(root.toPrettyString()); + if (code == 200){ + DettachedVerifySimpleResponse data = objectMapper.treeToValue(root.get("data"), DettachedVerifySimpleResponse.class); + return data.getGenericCertificate(); + } else if (code == 400){ + ErrorDataResponse errorData = objectMapper.treeToValue(root.get("data"), ErrorDataResponse.class); + throw new BusinessException("参数错误或签名失败(具体见返回码)", errorData); + } else if (code == 401){ + throw new RuntimeException("未认证或会话无效"); + } else{ + throw new RuntimeException("会话不存在"); + } + + } catch (IOException e) { + throw new RuntimeException("调用dettachedVerifySimple异常", e); + } + } + + + public boolean uploadCert(String bankCode, X509Certificate certificate){ + + String sessionId = UUID.randomUUID().toString().replace("-", ""); + + if (bankCode == null || bankCode.isBlank()) { + throw new IllegalArgumentException("origBytes不能为空"); + } + + if (certificate == null){ + throw new IllegalArgumentException("certificate不能为空"); + } + + + UploadCertRequest uploadCertRequest = new UploadCertRequest( + bankCode, + certificate + ); + uploadCertRequest.setSessionId(sessionId); + + String requestJson; + try { + requestJson = objectMapper.writeValueAsString(uploadCertRequest); + }catch (JsonProcessingException e){ + throw new IllegalArgumentException("serialize failed", e); + } + + RequestBody requestBody = RequestBody.create( + requestJson, + JSON_MEDIA_TYPE + ); + + Request request = new Request.Builder() + .url(buildUrl(properties.getUploadCertPath())) + .post(requestBody) + .addHeader("Content-Type", "application/json") + .addHeader("X-API-Key", properties.getApiKey()) + .build(); + + + try (Response response = okHttpClient.newCall(request).execute()) { + String responseBody = response.body() == null ? "" : response.body().string(); + JsonNode root = objectMapper.readTree(responseBody); + int code = root.get("code").asInt(); + + if (code == 200){ + UploadCertResponse data = objectMapper.treeToValue(root.get("data"), UploadCertResponse.class); + return data.getSuccess(); + } else if (code == 400){ + ErrorDataResponse errorData = objectMapper.treeToValue(root.get("data"), ErrorDataResponse.class); + throw new BusinessException("参数错误或签名失败(具体见返回码)", errorData); + } else if (code == 401){ + throw new RuntimeException("未认证或会话无效"); + } else{ + throw new RuntimeException("会话不存在"); + } + + } catch (IOException e) { + throw new RuntimeException("调用uploadCert异常", e); + } + } + + + public Integer lastReturnCode(){ + return lastReturnCode.get(); + } + + + private String buildUrl(String path) { + String baseUrl = properties.getBaseUrl(); + + if (baseUrl.endsWith("/") && path.startsWith("/")) { + return baseUrl.substring(0, baseUrl.length() - 1) + path; + } + + if (!baseUrl.endsWith("/") && !path.startsWith("/")) { + return baseUrl + "/" + path; + } + + return baseUrl + path; + } +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/config/OkHttpConfig.java b/openapi-sdk/src/main/java/com/cisd/openapi/config/OkHttpConfig.java new file mode 100644 index 0000000..b6665ef --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/config/OkHttpConfig.java @@ -0,0 +1,20 @@ +package com.cisd.openapi.config; + +import okhttp3.OkHttpClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.time.Duration; + +@Configuration +public class OkHttpConfig { + + @Bean + public OkHttpClient okHttpClient() { + return new OkHttpClient.Builder() + .connectTimeout(Duration.ofSeconds(5)) + .writeTimeout(Duration.ofSeconds(10)) + .readTimeout(Duration.ofSeconds(30)) + .build(); + } +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/config/SignServerProperties.java b/openapi-sdk/src/main/java/com/cisd/openapi/config/SignServerProperties.java new file mode 100644 index 0000000..51d8d7e --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/config/SignServerProperties.java @@ -0,0 +1,29 @@ +package com.cisd.openapi.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "sign-server") +@Data +public class SignServerProperties { + + private String baseUrl; + + private String apiKey; + + private String rawSignPath; + + private String rawVerifyPath; + + private String dettachedSignPath; + + private String dettachedVerifyPath; + + private String dettachedVerifySimplePath; + + private String uploadCertPath; + + private String lastReturnCodePath; +} \ No newline at end of file diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/CertificateInfo.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/CertificateInfo.java new file mode 100644 index 0000000..0685b06 --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/CertificateInfo.java @@ -0,0 +1,10 @@ +package com.cisd.openapi.dto; + +public class CertificateInfo { + private String subjectDN; + private String issuerDN; + private String serialNumber; + private String notBefore; + private String notAfter; + private String base64Encoded; +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedSignRequest.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedSignRequest.java new file mode 100644 index 0000000..84308e6 --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedSignRequest.java @@ -0,0 +1,19 @@ +package com.cisd.openapi.dto; + +import lombok.Data; + +@Data +public class DettachedSignRequest { + + private String sessionId; + + private String origBytes; + + private String dn; + + public DettachedSignRequest(String sessionId, String origBytes, String dn) { + this.sessionId = sessionId; + this.origBytes = origBytes; + this.dn = dn; + } +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedSignResponse.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedSignResponse.java new file mode 100644 index 0000000..42ef804 --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedSignResponse.java @@ -0,0 +1,9 @@ +package com.cisd.openapi.dto; + +import lombok.Data; + +@Data +public class DettachedSignResponse { + + private String signature; +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifyRequest.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifyRequest.java new file mode 100644 index 0000000..f8ea40a --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifyRequest.java @@ -0,0 +1,20 @@ +package com.cisd.openapi.dto; + +import lombok.Data; + +@Data +public class DettachedVerifyRequest { + + private String sessionId; + + private String origBytes; + + private String signature; + + + public DettachedVerifyRequest(String origBytes, String signature) { + this.origBytes = origBytes; + this.signature = signature; + } + +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifyResponse.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifyResponse.java new file mode 100644 index 0000000..981391e --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifyResponse.java @@ -0,0 +1,9 @@ +package com.cisd.openapi.dto; + +import lombok.Data; + +@Data +public class DettachedVerifyResponse { + + private GenericCertificate genericCertificate; +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifySimpleRequest.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifySimpleRequest.java new file mode 100644 index 0000000..c6e72c6 --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifySimpleRequest.java @@ -0,0 +1,19 @@ +package com.cisd.openapi.dto; + +import lombok.Data; + +@Data +public class DettachedVerifySimpleRequest { + + private String sessionId; + + private String origBytes; + + private String signature; + + public DettachedVerifySimpleRequest(String origBytes, String signature) { + this.origBytes = origBytes; + this.signature = signature; + } + +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifySimpleResponse.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifySimpleResponse.java new file mode 100644 index 0000000..6415584 --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DettachedVerifySimpleResponse.java @@ -0,0 +1,9 @@ +package com.cisd.openapi.dto; + +import lombok.Data; + +@Data +public class DettachedVerifySimpleResponse { + + private GenericCertificate genericCertificate; +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/DeviceActivateInfoResponse.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DeviceActivateInfoResponse.java new file mode 100644 index 0000000..69f844b --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DeviceActivateInfoResponse.java @@ -0,0 +1,19 @@ +package com.cisd.openapi.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class DeviceActivateInfoResponse { + + @Schema(description = "设备型号", example = "SYD7108") + private String deviceModel; + @Schema(description = "设备名称", example = "跨境支付终端一体机") + private String deviceName; + @Schema(description = "设备版本", example = "1.0.0") + private String version; + @Schema(description = "设备序列号", example = "1234567890ABCDEF") + private String serialNumber; + @Schema(description = "设备状态", example = "INITIALIZED") + private String deviceStatus; +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/DeviceActivateRequest.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DeviceActivateRequest.java new file mode 100644 index 0000000..6558eef --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/DeviceActivateRequest.java @@ -0,0 +1,11 @@ +package com.cisd.openapi.dto; + +import lombok.Data; + +@Data +public class DeviceActivateRequest { + + private String userId; + private String credential; + private String activationCode; +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/ErrorDataResponse.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/ErrorDataResponse.java new file mode 100644 index 0000000..4d8289d --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/ErrorDataResponse.java @@ -0,0 +1,9 @@ +package com.cisd.openapi.dto; + +import lombok.Data; + +@Data +public class ErrorDataResponse { + Integer code; + String message; +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/GenericCertificate.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/GenericCertificate.java new file mode 100644 index 0000000..0e36845 --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/GenericCertificate.java @@ -0,0 +1,123 @@ +package com.cisd.openapi.dto; + +import com.cisd.openapi.util.Base64; +import com.cisd.openapi.util.NetSignAgentUtil; +import com.cisd.openapi.util.PBCUtil; + +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; + + +public class GenericCertificate { + private String bankcode; + private String subject; + private String ser_number; + private String issuer_subject; + private String start_time; + private String end_time; + private X509Certificate cert; + + public GenericCertificate() { + } + + public GenericCertificate(X509Certificate cert) { + this.setCert(cert); + } + + public String getBankcode() { + return this.bankcode; + } + + public void setBankcode(String bankcode) { + this.bankcode = bankcode; + } + + public String getSubject() { + return this.subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getSer_number() { + return this.ser_number; + } + + public void setSer_number(String ser_number) { + this.ser_number = ser_number; + } + + public String getIssuer_subject() { + return this.issuer_subject; + } + + public void setIssuer_subject(String issuer_subject) { + this.issuer_subject = issuer_subject; + } + + public String getStart_time() { + return this.start_time; + } + + public void setStart_time(String start_time) { + this.start_time = start_time; + } + + public String getEnd_time() { + return this.end_time; + } + + public void setEnd_time(String end_time) { + this.end_time = end_time; + } + + public X509Certificate getCert() { + return this.cert; + } + + public String getBase64Cert() { + if (this.cert == null) { + return null; + } else { + try { + return Base64.encode(this.cert.getEncoded()); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + } + + public void setCert(X509Certificate cert) { + this.cert = cert; + this.subject = NetSignAgentUtil.getDN(cert.getSubjectDN().toString()); + this.ser_number = NetSignAgentUtil.dealSN(cert.getSerialNumber().toString(16).toUpperCase()); + this.issuer_subject = NetSignAgentUtil.getDN(cert.getIssuerDN().toString()); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + this.start_time = sdf.format(cert.getNotBefore()); + this.end_time = sdf.format(cert.getNotAfter()); + this.bankcode = PBCUtil.getBankID(this.subject); + } + + public String toString() { + StringBuffer s = new StringBuffer(); + s.append("=============BEGIN GenericCertificate===================\n"); + s.append("bankCode:").append(this.bankcode).append("\n"); + s.append("issuerSubject:").append(this.issuer_subject).append("\n"); + s.append("serialNumber:").append(this.ser_number).append("\n"); + s.append("subject:").append(this.subject).append("\n"); + s.append("startTime:").append(this.start_time).append("\n"); + s.append("endTime:").append(this.end_time).append("\n"); + if (this.cert != null) { + try { + s.append("cert:").append(Base64.encode(this.cert.getEncoded())).append("\n"); + } catch (Exception var3) { + } + } + + s.append("=============END GenericCertificate=====================\n"); + return s.toString(); + } +} + diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/LastReturnCodeRequest.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/LastReturnCodeRequest.java new file mode 100644 index 0000000..aaa613b --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/LastReturnCodeRequest.java @@ -0,0 +1,12 @@ +package com.cisd.openapi.dto; + +import lombok.Data; + +@Data +public class LastReturnCodeRequest { + private String sessionId; + + public LastReturnCodeRequest(String sessionId){ + this.sessionId = sessionId; + } +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/LastReturnCodeResponse.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/LastReturnCodeResponse.java new file mode 100644 index 0000000..18aed67 --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/LastReturnCodeResponse.java @@ -0,0 +1,8 @@ +package com.cisd.openapi.dto; + +import lombok.Data; + +@Data +public class LastReturnCodeResponse { + private Integer returnCode; +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/RawSignRequest.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/RawSignRequest.java new file mode 100644 index 0000000..2ead011 --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/RawSignRequest.java @@ -0,0 +1,19 @@ +package com.cisd.openapi.dto; + + +import lombok.Data; + +@Data +public class RawSignRequest { + + private String sessionId; + + private String origBytes; + + private String dn; + + public RawSignRequest(String origBytes, String dn) { + this.origBytes = origBytes; + this.dn = dn; + } +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/RawSignResponse.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/RawSignResponse.java new file mode 100644 index 0000000..6510c5a --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/RawSignResponse.java @@ -0,0 +1,8 @@ +package com.cisd.openapi.dto; + +import lombok.Data; + +@Data +public class RawSignResponse { + private String signature; +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/RawVerifyRequest.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/RawVerifyRequest.java new file mode 100644 index 0000000..b003a67 --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/RawVerifyRequest.java @@ -0,0 +1,24 @@ +package com.cisd.openapi.dto; + + +import lombok.Data; + +@Data +public class RawVerifyRequest { + + private String sessionId; + + private String origBytes; + + private String dn; + + private String signature; + + public RawVerifyRequest(String origBytes, String dn, String signature) { + this.origBytes = origBytes; + this.dn = dn; + this.signature = signature; + } + + +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/RawVerifyResponse.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/RawVerifyResponse.java new file mode 100644 index 0000000..2c3b53c --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/RawVerifyResponse.java @@ -0,0 +1,10 @@ +package com.cisd.openapi.dto; + + +import lombok.Data; + +@Data +public class RawVerifyResponse { + private Boolean verified; + private String message; +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/UploadCertRequest.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/UploadCertRequest.java new file mode 100644 index 0000000..c317cfd --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/UploadCertRequest.java @@ -0,0 +1,18 @@ +package com.cisd.openapi.dto; + +import lombok.Data; + +import java.security.cert.X509Certificate; + +@Data +public class UploadCertRequest { + + private String sessionId; + private String bankCode; + private X509Certificate certificate; + + public UploadCertRequest(String bankCode, X509Certificate certificate) { + this.bankCode = bankCode; + this.certificate = certificate; + } +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/dto/UploadCertResponse.java b/openapi-sdk/src/main/java/com/cisd/openapi/dto/UploadCertResponse.java new file mode 100644 index 0000000..0a159ea --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/dto/UploadCertResponse.java @@ -0,0 +1,9 @@ +package com.cisd.openapi.dto; + +import lombok.Data; + +@Data +public class UploadCertResponse { + + private Boolean success; +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/exception/BusinessException.java b/openapi-sdk/src/main/java/com/cisd/openapi/exception/BusinessException.java new file mode 100644 index 0000000..4cecad0 --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/exception/BusinessException.java @@ -0,0 +1,16 @@ +package com.cisd.openapi.exception; + +import com.cisd.openapi.dto.ErrorDataResponse; + +public class BusinessException extends RuntimeException { + private final ErrorDataResponse errorData; + + public BusinessException(String message, ErrorDataResponse errorData) { + super(message); + this.errorData = errorData; + } + + public ErrorDataResponse getErrorData() { + return errorData; + } +} \ No newline at end of file diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/util/Base64.java b/openapi-sdk/src/main/java/com/cisd/openapi/util/Base64.java new file mode 100644 index 0000000..cbc7bde --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/util/Base64.java @@ -0,0 +1,324 @@ +package com.cisd.openapi.util; + +import java.util.Arrays; + +public class Base64 { + private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); + private static final int[] IA = new int[256]; + + static { + Arrays.fill(IA, -1); + int i = 0; + + for (int iS = CA.length; i < iS; IA[CA[i]] = i++) { + } + + IA[61] = 0; + } + + public static final String encode(byte[] sArr) { + return new String(encodeToString(sArr, false)); + } + + private static final char[] encodeToChar(byte[] sArr, boolean lineSep) { + int sLen = sArr != null ? sArr.length : 0; + if (sLen == 0) { + return new char[0]; + } else { + int eLen = sLen / 3 * 3; + int cCnt = (sLen - 1) / 3 + 1 << 2; + int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); + char[] dArr = new char[dLen]; + int s = 0; + int d = 0; + int cc = 0; + + while (s < eLen) { + int i = (sArr[s++] & 255) << 16 | (sArr[s++] & 255) << 8 | sArr[s++] & 255; + dArr[d++] = CA[i >>> 18 & 63]; + dArr[d++] = CA[i >>> 12 & 63]; + dArr[d++] = CA[i >>> 6 & 63]; + dArr[d++] = CA[i & 63]; + if (lineSep) { + ++cc; + if (cc == 19 && d < dLen - 2) { + dArr[d++] = '\r'; + dArr[d++] = '\n'; + cc = 0; + } + } + } + + s = sLen - eLen; + if (s > 0) { + d = (sArr[eLen] & 255) << 10 | (s == 2 ? (sArr[sLen - 1] & 255) << 2 : 0); + dArr[dLen - 4] = CA[d >> 12]; + dArr[dLen - 3] = CA[d >>> 6 & 63]; + dArr[dLen - 2] = s == 2 ? CA[d & 63] : 61; + dArr[dLen - 1] = '='; + } + + return dArr; + } + } + + public static final byte[] decode(char[] sArr) { + char[] tmp = new char[sArr.length]; + int t = 0; + int i = 0; + + for (int length = sArr.length; i < length; ++i) { + if (sArr[i] >= 'A' && sArr[i] <= 'Z' || sArr[i] >= 'a' && sArr[i] <= 'z' || sArr[i] >= '0' && sArr[i] <= '9' || sArr[i] == '+' || sArr[i] == '/' || sArr[i] == '=') { + tmp[t] = sArr[i]; + ++t; + } + } + + char[] tmp2 = new char[t]; + System.arraycopy(tmp, 0, tmp2, 0, t); + tmp = (char[]) null; + return decodeFast(tmp2); + } + + private static final byte[] decodeFast(char[] sArr) { + int sLen = sArr.length; + if (sLen == 0) { + return new byte[0]; + } else { + int sIx = 0; + + int eIx; + for (eIx = sLen - 1; sIx < eIx && IA[sArr[sIx]] < 0; ++sIx) { + } + + while (eIx > 0 && IA[sArr[eIx]] < 0) { + --eIx; + } + + int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; + int cCnt = eIx - sIx + 1; + int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; + byte[] dArr = new byte[len]; + int d = 0; + int cc = 0; + int eLen = len / 3 * 3; + + while (d < eLen) { + int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]]; + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + if (sepCnt > 0) { + ++cc; + if (cc == 19) { + sIx += 2; + cc = 0; + } + } + } + + if (d < len) { + cc = 0; + + for (int j = 0; sIx <= eIx - pad; ++j) { + cc |= IA[sArr[sIx++]] << 18 - j * 6; + } + + for (int r = 16; d < len; r -= 8) { + dArr[d++] = (byte) (cc >> r); + } + } + + return dArr; + } + } + + private static final byte[] encodeToByte(byte[] sArr, boolean lineSep) { + int sLen = sArr != null ? sArr.length : 0; + if (sLen == 0) { + return new byte[0]; + } else { + int eLen = sLen / 3 * 3; + int cCnt = (sLen - 1) / 3 + 1 << 2; + int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); + byte[] dArr = new byte[dLen]; + int s = 0; + int d = 0; + int cc = 0; + + while (s < eLen) { + int i = (sArr[s++] & 255) << 16 | (sArr[s++] & 255) << 8 | sArr[s++] & 255; + dArr[d++] = (byte) CA[i >>> 18 & 63]; + dArr[d++] = (byte) CA[i >>> 12 & 63]; + dArr[d++] = (byte) CA[i >>> 6 & 63]; + dArr[d++] = (byte) CA[i & 63]; + if (lineSep) { + ++cc; + if (cc == 19 && d < dLen - 2) { + dArr[d++] = 13; + dArr[d++] = 10; + cc = 0; + } + } + } + + s = sLen - eLen; + if (s > 0) { + d = (sArr[eLen] & 255) << 10 | (s == 2 ? (sArr[sLen - 1] & 255) << 2 : 0); + dArr[dLen - 4] = (byte) CA[d >> 12]; + dArr[dLen - 3] = (byte) CA[d >>> 6 & 63]; + dArr[dLen - 2] = s == 2 ? (byte) CA[d & 63] : 61; + dArr[dLen - 1] = 61; + } + + return dArr; + } + } + + public static final byte[] decode(byte[] sArr) { + byte[] tmp = new byte[sArr.length]; + int t = 0; + int i = 0; + + for (int length = sArr.length; i < length; ++i) { + if (sArr[i] >= 65 && sArr[i] <= 90 || sArr[i] >= 97 && sArr[i] <= 122 || sArr[i] >= 48 && sArr[i] <= 57 || sArr[i] == 43 || sArr[i] == 47 || sArr[i] == 61) { + tmp[t] = sArr[i]; + ++t; + } + } + + byte[] tmp2 = new byte[t]; + System.arraycopy(tmp, 0, tmp2, 0, t); + tmp = (byte[]) null; + return decodeFast(tmp2); + } + + private static final byte[] decodeFast(byte[] sArr) { + int sLen = sArr.length; + if (sLen == 0) { + return new byte[0]; + } else { + int sIx = 0; + + int eIx; + for (eIx = sLen - 1; sIx < eIx && IA[sArr[sIx] & 255] < 0; ++sIx) { + } + + while (eIx > 0 && IA[sArr[eIx] & 255] < 0) { + --eIx; + } + + int pad = sArr[eIx] == 61 ? (sArr[eIx - 1] == 61 ? 2 : 1) : 0; + int cCnt = eIx - sIx + 1; + int sepCnt = sLen > 76 ? (sArr[76] == 13 ? cCnt / 78 : 0) << 1 : 0; + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; + byte[] dArr = new byte[len]; + int d = 0; + int cc = 0; + int eLen = len / 3 * 3; + + while (d < eLen) { + int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]]; + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + if (sepCnt > 0) { + ++cc; + if (cc == 19) { + sIx += 2; + cc = 0; + } + } + } + + if (d < len) { + cc = 0; + + for (int j = 0; sIx <= eIx - pad; ++j) { + cc |= IA[sArr[sIx++]] << 18 - j * 6; + } + + for (int r = 16; d < len; r -= 8) { + dArr[d++] = (byte) (cc >> r); + } + } + + return dArr; + } + } + + private static final String encodeToString(byte[] sArr, boolean lineSep) { + return new String(encodeToChar(sArr, lineSep)); + } + + public static final byte[] decode(String sArr) { + return decode(sArr.getBytes()); + } + + private static final byte[] decodeFast(String s) { + int sLen = s.length(); + if (sLen == 0) { + return new byte[0]; + } else { + int sIx = 0; + + int eIx; + for (eIx = sLen - 1; sIx < eIx && IA[s.charAt(sIx) & 255] < 0; ++sIx) { + } + + while (eIx > 0 && IA[s.charAt(eIx) & 255] < 0) { + --eIx; + } + + int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0; + int cCnt = eIx - sIx + 1; + int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0; + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; + byte[] dArr = new byte[len]; + int d = 0; + int cc = 0; + int eLen = len / 3 * 3; + + while (d < eLen) { + int i = IA[s.charAt(sIx++)] << 18 | IA[s.charAt(sIx++)] << 12 | IA[s.charAt(sIx++)] << 6 | IA[s.charAt(sIx++)]; + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + if (sepCnt > 0) { + ++cc; + if (cc == 19) { + sIx += 2; + cc = 0; + } + } + } + + if (d < len) { + cc = 0; + + for (int j = 0; sIx <= eIx - pad; ++j) { + cc |= IA[s.charAt(sIx++)] << 18 - j * 6; + } + + for (int r = 16; d < len; r -= 8) { + dArr[d++] = (byte) (cc >> r); + } + } + + return dArr; + } + } + + public static void main(String[] args) { + String a = "abcabc"; + String b64 = encode(a.getBytes()); + System.out.println(b64); + b64 = "YWJj\r\nYWJj\r\nYWJj\r\n"; + byte[] x = decode(b64.getBytes()); + System.out.println(encode(x)); + byte[] plain = new byte[]{18, 52}; + char[] var10000 = new char[]{'\u0012', '4'}; + System.out.println(encode(plain)); + } +} \ No newline at end of file diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/util/DNItemReverseUtil.java b/openapi-sdk/src/main/java/com/cisd/openapi/util/DNItemReverseUtil.java new file mode 100644 index 0000000..34488ba --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/util/DNItemReverseUtil.java @@ -0,0 +1,117 @@ +package com.cisd.openapi.util; + +import java.util.Stack; +import java.util.StringTokenizer; + +public class DNItemReverseUtil { + private static final String[] dNObjectsForward = new String[]{"1.2.840.113549.1.9.8", "1.2.840.113549.1.9.2", "emailaddress", "e", "email", "uid", "cn", "sn", "serialnumber", "gn", "givenname", "initials", "surname", "t", "ou", "o", "l", "st", "dc", "c"}; + private static final String[] dNObjectsReverse = new String[]{"c", "dc", "st", "l", "o", "ou", "t", "surname", "initials", "givenname", "gn", "serialnumber", "sn", "cn", "uid", "email", "e", "emailaddress", "1.2.840.113549.1.9.2", "1.2.840.113549.1.9.8"}; + private static final String[] dNObjects; + + static { + dNObjects = dNObjectsForward; + } + + public static String reverse(String dn) { + StringTokenizer st = new StringTokenizer(dn, ","); + Stack s = new Stack(); + + while(st.hasMoreTokens()) { + s.push(st.nextToken()); + } + + int len = s.size(); + StringBuffer buffer = new StringBuffer(); + + for(int i = 0; i < len; ++i) { + if (i > 0) { + buffer.append(","); + } + + buffer.append((String)s.pop()); + } + + return buffer.toString(); + } + +// public static String reverseDN(String dn) { +// String ret = null; +// if (dn != null) { +// BasicX509NameTokenizer xt = new BasicX509NameTokenizer(dn); +// StringBuffer buf = new StringBuffer(); +// +// String o; +// for(boolean first = true; xt.hasMoreTokens(); buf.insert(0, o)) { +// o = xt.nextToken(); +// if (!first) { +// buf.insert(0, ","); +// } else { +// first = false; +// } +// } +// +// if (buf.length() > 0) { +// ret = buf.toString(); +// } +// } +// +// return ret; +// } + + public static String reverseDN2(String dn) { + if (dn == null) { + return null; + } else { + StringBuffer sb = new StringBuffer(); + String[] tmp = dn.split("(?= 0; --i) { + sb.append(tmp[i]); + if (i > 0) { + sb.append(","); + } + } + + return sb.toString(); + } + } + + public static boolean isDNReversed(String dn) { + boolean ret = false; + if (dn != null) { + String first = null; + String last = null; + X509NameTokenizer xt = new X509NameTokenizer(dn); + if (xt.hasMoreTokens()) { + first = xt.nextToken(); + } + + while(xt.hasMoreTokens()) { + last = xt.nextToken(); + } + + if (first != null && last != null) { + first = first.substring(0, first.indexOf(61)); + last = last.substring(0, last.indexOf(61)); + int firsti = 0; + int lasti = 0; + + for(int i = 0; i < dNObjects.length; ++i) { + if (first.toLowerCase().equals(dNObjectsForward[i])) { + firsti = i; + } + + if (last.toLowerCase().equals(dNObjectsForward[i])) { + lasti = i; + } + } + + if (lasti < firsti) { + ret = true; + } + } + } + + return ret; + } +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/util/NetSignAgentRes.java b/openapi-sdk/src/main/java/com/cisd/openapi/util/NetSignAgentRes.java new file mode 100644 index 0000000..0783f9f --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/util/NetSignAgentRes.java @@ -0,0 +1,629 @@ +package com.cisd.openapi.util; + +// +//import cn.com.infosec.netsign.agent.communication.APIToServer; +//import cn.com.infosec.netsign.agent.exception.NetSignAgentException; +//import cn.com.infosec.netsign.agent.newcommunitor.CommunitorManager; +//import cn.com.infosec.netsign.agent.service.NSService; +//import cn.com.infosec.netsign.crypto.util.SoftCryptoImpl; +//import cn.com.infosec.netsign.frame.config.ExtendedConfig; +//import cn.com.infosec.netsign.logger.ConsoleLogger; + +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +public class NetSignAgentRes { + private static ResourceBundle resBundle = null; + static boolean hasDone = false; + private static int communicateMode = 1; + private static boolean zipMode = false; + private static int minZipSize = 200; + private static byte[] secKey = null; + private static byte[] keyHash = null; + private static byte[] encSecKey = null; + private static String clientId = null; + private static int seq = 0; + private static String encoding; + private static int orderDN = 0; + private static int snmode = 1; + public static final int CONNECTION_MODE_CONCENTRATE = 1; + public static final int CONNECTION_MODE_HASH = 2; + public static final int CONNECTION_MODE_ORDER = 3; + public static final int CONNECTION_MODE_POLLING = 4; + private static int connectionMode = 1; + private static boolean autoTest = false; + private static long testInterval = 30000L; + private static boolean logConnection = false; + private static boolean poolFlag = false; + private static int maxPoolSize = 20; + private static int retryTimes = 1; + private static int retryInterval = 500; + private static int encLock = 8; + private static int maxWorkingThread = -1; + + static { + initNewConnectionParameters(); + } + + public static void initNewConnectionParameters() { + String str = System.getProperty("cn.com.infosec.netsign.agent.servicetest.interval"); + if (str != null) { + try { + testInterval = Long.parseLong(str); + } catch (Exception var3) { + } + } + + str = System.getProperty("cn.com.infosec.netsign.agent.servicetest.isautotest"); + if (str != null) { + autoTest = str.equals("yes"); + } + + str = System.getProperty("cn.com.infosec.netsign.agent.connection.mode"); + if (str != null) { + try { + connectionMode = Integer.parseInt(str); + } catch (Exception var2) { + } + } + + } + +// static synchronized void initialize(ResourceBundle res) throws NetSignAgentException { +// if (!hasDone) { +// System.out.println("NetSignServerAPI initialize start..."); +// if (resBundle == null) { +// resBundle = getResourceBundle(res); +// } +// +// initConnections(); +// if (initPooledFlag().equals("true") && initHeartBeat()) { +// if (APIToServer.timeout == 0) { +// boolean var10000 = true; +// } else { +// int var4 = APIToServer.timeout / 2; +// } +// } +// +// long gc = initGC(); +// if (gc != 0L) { +// AutoGC tgc = new AutoGC(gc); +// tgc.start(); +// } +// +// clientId = initClientId(); +// communicateMode = initCommunicateMode(); +// zipMode = initZipMode(); +// if (zipMode) { +// minZipSize = initMinZipSize(); +// } +// +// if (communicateMode == 0) { +// initSecKey(); +// } +// +// encoding = initEncoding() != null && !"".equals(initEncoding()) ? initEncoding() : encoding; +// if (encoding != null && !"".equals(encoding)) { +// ExtendedConfig.setEncoding(encoding); +// } else { +// ExtendedConfig.setEncoding("GBK"); +// } +// +// orderDN = initOrderDN(); +// snmode = initSNMode(); +// hasDone = true; +// encLock = initEncryptLock(); +// maxWorkingThread = initMaxParallelThread(); +// System.out.println("NetSignServerAPI initialize finished..."); +// } +// +// } + + public static void setMaxWorkingThread(int count) { + maxWorkingThread = count; + } + + public static int getMaxParallelThread() { + return maxWorkingThread; + } + + public static int getEncLock() { + return encLock; + } + + static int initMaxParallelThread() { + try { + String tmp = resBundle.getString("maxparallelthread"); + return Integer.parseInt(tmp); + } catch (Exception var1) { + return maxWorkingThread; + } + } + + static int initEncryptLock() { + try { + String tmp = resBundle.getString("encryptlock"); + return Integer.parseInt(tmp); + } catch (Exception var1) { + return encLock; + } + } + +// static synchronized void initialize(NetSignService service) throws NetSignAgentException { +// if (!hasDone) { +// System.out.println("NetSignServerAPI initialize start..."); +// initConnections(service.getHost(), service.getPort(), service.getPoolSize(), service.getTimeout()); +// hasDone = true; +// System.out.println("NetSignServerAPI initialize finished..."); +// } +// +// } +// +// private static String initServerIP() throws NetSignAgentException { +// try { +// return resBundle.getString("ServerIP").trim(); +// } catch (MissingResourceException var1) { +// throw new NetSignAgentException("ServerIP can not be null"); +// } +// } +// +// private static String initTimeout() throws NetSignAgentException { +// try { +// return resBundle.getString("Timeout").trim(); +// } catch (Exception var1) { +// return "30000"; +// } +// } +// +// private static String initConnectTimeout() throws NetSignAgentException { +// try { +// return resBundle.getString("ConnectTimeout").trim(); +// } catch (Exception var1) { +// return "30000"; +// } +// } +// +// private static String initServerPort() throws NetSignAgentException { +// try { +// return resBundle.getString("ServerPort").trim(); +// } catch (MissingResourceException var1) { +// throw new NetSignAgentException("ServerPort can not be null"); +// } +// } +// +// private static int initCommunicateMode() throws NetSignAgentException { +// try { +// return Integer.parseInt(resBundle.getString("CommunicateMode").trim()); +// } catch (MissingResourceException var1) { +// throw new NetSignAgentException("CommunicateMode can not be null"); +// } catch (NumberFormatException var2) { +// throw new NetSignAgentException("Invalid CommunicateMode value"); +// } +// } +// +// private static String initMaxConnections() throws NetSignAgentException { +// try { +// String max = resBundle.getString("MaxConnections").trim(); +// maxPoolSize = Integer.parseInt(max); +// return max; +// } catch (MissingResourceException var1) { +// maxPoolSize = 20; +// return "20"; +// } +// } +// +// private static boolean initZipMode() throws NetSignAgentException { +// try { +// return Boolean.valueOf(resBundle.getString("ZipMode").trim()); +// } catch (MissingResourceException var1) { +// throw new NetSignAgentException("ZipMode can not be null"); +// } +// } +// +// private static int initMinZipSize() throws NetSignAgentException { +// try { +// return 1024 * Integer.parseInt(resBundle.getString("MinZipSize").trim()); +// } catch (MissingResourceException var1) { +// throw new NetSignAgentException("MinZipSize can not be null"); +// } catch (NumberFormatException var2) { +// throw new NetSignAgentException("Invalid MinZipSize value"); +// } +// } +// +// private static void initSecKey() throws NetSignAgentException { +// String certFile = null; +// +// try { +// certFile = resBundle.getString("ServerEncCert").trim(); +// } catch (MissingResourceException var17) { +// throw new NetSignAgentException("ServerEncCert can not be null"); +// } +// +// FileInputStream in = null; +// +// try { +// CertificateFactory factory = CertificateFactory.getInstance("X.509", "INFOSEC"); +// in = new FileInputStream(certFile); +// System.out.println("comm cert:" + certFile + " length:" + in.available()); +// X509Certificate cert = (X509Certificate)factory.generateCertificate(in); +// secKey = SoftCryptoImpl.genSecretKey(SoftCryptoImpl.RC4, 128); +// keyHash = SoftCryptoImpl.hash("SHA1", secKey); +// encSecKey = SoftCryptoImpl.encryptWithRSAPubkey(cert.getPublicKey(), secKey); +// } catch (FileNotFoundException var14) { +// throw new NetSignAgentException("Can not found cert file: " + certFile); +// } catch (Exception ex) { +// ConsoleLogger.logException(ex); +// throw new NetSignAgentException("Generate SecKey Error: " + ex.getMessage()); +// } finally { +// try { +// if (in != null) { +// in.close(); +// } +// } catch (Exception e) { +// e.printStackTrace(); +// } +// +// } +// +// } + + private static String initClientId() { + try { + return resBundle.getString("ClientID").trim(); + } catch (MissingResourceException var1) { + return null; + } + } + + private static String initPooledFlag() { + try { + String flag = resBundle.getString("UsedConnectionPool").trim(); + poolFlag = Boolean.valueOf(flag); + return flag; + } catch (Exception var1) { + poolFlag = false; + return "false"; + } + } + +// private static void initConnections() throws NetSignAgentException { +// initPooledFlag(); +// initMaxConnections(); +// initConnectTimeout(); +// initServiceTestIsTest(); +// initConnectionMode(); +// initConnectionLog(); +// initServiceTestInterval(); +// initRetryTimes(); +// initRetryInerval(); +// NetSignAgent.cm = new CommunitorManager(false); +// String ip = initServerIP(); +// String port = initServerPort(); +// String timeout = initTimeout(); +// String[] ips = ip.split(","); +// String[] ports = port.split(","); +// String[] timeouts = timeout.split(","); +// +// for(int i = 0; i < ips.length; ++i) { +// NSService s = new NSService(); +// s.setIp(ips[i].trim()); +// s.setPort(Integer.parseInt(ports[i].trim())); +// s.setTimeout(Integer.parseInt(timeouts[i].trim())); +// NetSignAgent.cm.addService(s); +// } +// +// } + + private static void initConnectionLog() { + try { + String str = resBundle.getString("logconnection"); + if (str != null && (str.equalsIgnoreCase("true") || str.equalsIgnoreCase("yes"))) { + logConnection = true; + } + } catch (Exception var1) { + } + + } + + private static void initRetryTimes() { + try { + String str = resBundle.getString("retrytimes"); + if (str != null) { + retryTimes = Integer.parseInt(str); + } + } catch (Exception var1) { + } + + } + + private static void initRetryInerval() { + try { + String str = resBundle.getString("retryinterval"); + if (str != null) { + retryInterval = Integer.parseInt(str); + } + } catch (Exception var1) { + } + + } + + private static void initServiceTestIsTest() { + try { + String str = resBundle.getString("servicetestistest"); + if (str != null) { + autoTest = "yes".equals(str.toLowerCase()) || "true".equals(str.toLowerCase()); + } + } catch (Exception var1) { + } + + } + + private static void initServiceTestInterval() { + try { + String str = resBundle.getString("servicetestinterval"); + if (str != null) { + try { + testInterval = (long)Integer.parseInt(str); + } catch (Exception var2) { + } + } + } catch (Exception var3) { + } + + } + + private static void initConnectionMode() { + try { + String str = resBundle.getString("connectionmode"); + connectionMode = Integer.parseInt(str); + } catch (Exception var1) { + } + + if (connectionMode <= 0) { + connectionMode = 3; + } + + } + + private static boolean initHeartBeat() { + try { + String start = resBundle.getString("heartbeat").trim(); + return start.equals("true"); + } catch (Exception var1) { + return false; + } + } + + private static long initGC() { + try { + String gc = resBundle.getString("gc").trim(); + return Long.parseLong(gc); + } catch (Exception var1) { + return 60L; + } + } + + private static String initEncoding() { + try { + return resBundle.getString("encoding").trim(); + } catch (Exception var1) { + return null; + } + } + + private static int initOrderDN() { + try { + return Integer.parseInt(resBundle.getString("orderdn")); + } catch (Exception var1) { + return 0; + } + } + + private static int initSNMode() { + try { + return Integer.parseInt(resBundle.getString("snmode")); + } catch (Exception var1) { + return 1; + } + } + +// public static void initConnections(String ip, int port, int maxConns, int timeout) throws NetSignAgentException { +// if (!hasDone) { +// Properties p = new Properties(); +// p.setProperty("ip", ip); +// p.setProperty("port", String.valueOf(port)); +// p.setProperty("pooled", "true"); +// if (maxConns < 1) { +// maxConns = 50; +// } +// +// p.setProperty("maxconn", String.valueOf(maxConns)); +// if (timeout < 1) { +// timeout = 0; +// } +// +// p.setProperty("timeout", String.valueOf(timeout)); +// APIToServer.initConnections(p); +// hasDone = true; +// } +// +// } +// +// public static void setClientId(String id) { +// clientId = id; +// } +// +// public static void setEncryptMode(String encCertFile) throws NetSignAgentException { +// communicateMode = 0; +// String certFile = encCertFile; +// +// try { +// CertificateFactory factory = CertificateFactory.getInstance("X.509", "INFOSEC"); +// new File(certFile); +// FileInputStream in = new FileInputStream(certFile); +// X509Certificate cert = (X509Certificate)factory.generateCertificate(in); +// in.close(); +// secKey = SoftCryptoImpl.genSecretKey(SoftCryptoImpl.RC4, 128); +// keyHash = SoftCryptoImpl.hash("SHA1", secKey); +// encSecKey = SoftCryptoImpl.encryptWithRSAPubkey(cert.getPublicKey(), secKey); +// } catch (FileNotFoundException fileEx) { +// fileEx.printStackTrace(); +// throw new NetSignAgentException("Can not found cert file: " + encCertFile); +// } catch (Exception ex) { +// ex.printStackTrace(); +// throw new NetSignAgentException("Generate SecKey Error: " + ex.getMessage()); +// } +// } + + public static void setZipMode(int zipSize) { + zipMode = true; + if (zipSize > 1) { + minZipSize = zipSize * 1024; + } + + } + + static String getClientId() { + return clientId; + } + + static int getCommunicateMode() { + return communicateMode; + } + + static boolean isZipMode() { + return zipMode; + } + + static int getMinZipSize() { + return minZipSize; + } + + static byte[] getSecKey() { + return secKey; + } + + static byte[] getKeyHash() { + return keyHash; + } + + static byte[] getEncSecKey() { + return encSecKey; + } + + static int getOrderDN() { + return orderDN; + } + + static int getSNMode() { + return snmode; + } + + public static void setLogConnection(boolean on) { + logConnection = on; + } + + public static boolean isLogConnection() { + return logConnection; + } + + public static int getRetryTimes() { + return retryTimes; + } + + public static int getRetryInterval() { + return retryInterval; + } + + public static void setEncoding(String encoding) { + NetSignAgentRes.encoding = encoding; + } + + public static boolean isUseConnectionPool() { + return poolFlag; + } + + public static void setUseConnectionPool(boolean ispool) { + poolFlag = ispool; + } + + public static int getMaxPoolSize() { + return maxPoolSize; + } + + public static void setMaxPoolSize(int poolsize) { + maxPoolSize = poolsize; + } + + public static void setAutoTest(boolean isautotest) { + autoTest = isautotest; + } + + public static boolean isAutoTest() { + return autoTest; + } + + public static void setConnectionMode(int mode) { + connectionMode = mode; + } + + public static int getConnectionMode() { + return connectionMode; + } + + public static void setTestInterval(long interval) { + testInterval = interval; + } + + public static long getTestInterval() { + return testInterval; + } + + static void setOrderDN(int orderdn) { + orderDN = orderdn; + } + + static String getSeq() { + ++seq; + seq &= Integer.MAX_VALUE; + return String.valueOf(seq % 100000 + 100000).substring(1); + } + + public static String getEncoding() { + return encoding; + } + +// private static ResourceBundle getResourceBundle(ResourceBundle res) throws NetSignAgentException { +// ResourceBundle bundle = null; +// System.out.println("vvvvvvvvvvvvvvvv NetSignAgent ENV vvvvvvvvvvvvvvvvv"); +// +// try { +// ClassLoader cl = ClassLoader.getSystemClassLoader(); +// URL[] urls = ((URLClassLoader)cl).getURLs(); +// if (urls != null) { +// int i = 0; +// +// for(int length = urls.length; i < length; ++i) { +// System.out.println(urls[i].getFile()); +// } +// } +// } catch (Throwable t) { +// t.printStackTrace(System.out); +// } +// +// System.out.println("^^^^^^^^^^^^^^^^^ NetSignAgent ENV ^^^^^^^^^^^^^^^^"); +// +// try { +// if (res != null) { +// bundle = res; +// } else { +// bundle = ResourceBundle.getBundle("netsignagent"); +// } +// +// return bundle; +// } catch (Exception miss1ex) { +// miss1ex.printStackTrace(System.out); +// throw new NetSignAgentException("Can not Found Config File netsignagent.properties"); +// } +// } +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/util/NetSignAgentUtil.java b/openapi-sdk/src/main/java/com/cisd/openapi/util/NetSignAgentUtil.java new file mode 100644 index 0000000..258cda8 --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/util/NetSignAgentUtil.java @@ -0,0 +1,819 @@ +package com.cisd.openapi.util; + +//import cn.com.infosec.jce.provider.*; +//import cn.com.infosec.jce.provider.fastparser.DerUtil; +//import cn.com.infosec.jce.provider.fastparser.FastPkcs7; +//import cn.com.infosec.jce.provider.fastparser.Item; +//import cn.com.infosec.netsign.agent.exception.NetSignAgentException; +//import cn.com.infosec.netsign.base.AbstractMessage; +//import cn.com.infosec.netsign.base.NSMessage; +//import cn.com.infosec.netsign.base.NSMessageOpt; +//import cn.com.infosec.netsign.base.processors.util.GZipUtil; +//import cn.com.infosec.netsign.crypto.util.Base64; +//import cn.com.infosec.netsign.crypto.util.SoftCryptoImpl; +//import cn.com.infosec.netsign.logger.ConsoleLogger; +//import cn.com.infosec.netsign.pool.Pool; +//import cn.com.infosec.netsign.pool.Poolable; +//import cn.com.infosec.oscca.encryption.SM4CBC; +//import cn.com.infosec.oscca.encryption.SM4ECB; +//import cn.com.infosec.oscca.sm2.SM2PublicKey; + +import org.w3c.dom.Document; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPublicKey; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class NetSignAgentUtil { + public static HashMap digestOIDName; +// public static Pool encLockPool; + +// static { +// if (Security.getProvider("INFOSEC") == null) { +// Security.addProvider(new InfosecProvider()); +// } +// +// digestOIDName = new HashMap(); +// digestOIDName.put("1.3.14.3.2.26", "SHA1"); +// digestOIDName.put("1.2.840.113549.2.5", "MD5"); +// digestOIDName.put("1.2.840.113549.2.2", "MD2"); +// digestOIDName.put("2.16.840.1.101.3.4.2.4", "SHA224"); +// digestOIDName.put("2.16.840.1.101.3.4.2.1", "SHA256"); +// digestOIDName.put("2.16.840.1.101.3.4.2.2", "SHA384"); +// digestOIDName.put("2.16.840.1.101.3.4.2.3", "SHA512"); +// digestOIDName.put("1.2.840.113549.11.5", "SHA1"); +// digestOIDName.put("1.2.840.113549.1.1.11", "SHA256"); +// digestOIDName.put("1.2.156.197.1.401", "SM3"); +// digestOIDName.put("1.2.156.10197.1.401", "SM3"); +// digestOIDName.put("1.2.156.10197.1.501", "SM3"); +// digestOIDName.put("1.2.156.197.1.401.2", "SM3-2"); +// digestOIDName.put("1.2.156.10197.1.401.2", "SM3-2"); +// digestOIDName.put("1.2.156.197.1.401.1", "SM3-1"); +// digestOIDName.put("1.2.156.10197.1.401.1", "SM3-1"); +// +// try { +// encLockPool = new Pool("cn.com.infosec.netsign.pool.SamplePoolable", NetSignAgentRes.getEncLock(), (Object)null); +// } catch (Exception e) { +// e.printStackTrace(System.out); +// } +// +// } + +// public static X509Certificate getCertificateFromP7(String p7b) { +// return getCertificateFromP7(Base64.decode(p7b)); +// } + +// public static X509Certificate getCertificateFromP7(byte[] p7b) { +// try { +// FastPkcs7 fxPKCS7 = new FastPkcs7(); +// fxPKCS7.pkcs7SignedData(p7b); +// Item signCertItem = fxPKCS7.getSignCert(); +// byte[] bsCert = DerUtil.getItemDataAndTag(p7b, signCertItem); +// return new X509CertificateObjectFX(bsCert); +// } catch (Exception e) { +// e.printStackTrace(System.out); +// return null; +// } +// } + + public static byte[] xmlCanonical(byte[] xml) throws Exception { + ByteArrayInputStream in = null; + ByteArrayOutputStream out = null; + + byte[] var11; + try { + in = new ByteArrayInputStream(xml); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document doc = db.parse(in); + DOMSource domSource = new DOMSource(doc.getDocumentElement()); + out = new ByteArrayOutputStream(); + StreamResult result = new StreamResult(out); + Transformer tf = TransformerFactory.newInstance().newTransformer(); + tf.transform(domSource, result); + var11 = out.toByteArray(); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (Exception var19) { + } + + try { + if (out != null) { + out.close(); + } + } catch (Exception var18) { + } + + } + + return var11; + } + +// public static String encode(byte[] bytes) { +// return Base64.encode(bytes); +// } +// +// public static byte[] decode(String text) throws NetSignAgentException { +// try { +// return Base64.decode(text); +// } catch (Exception e) { +// throw new NetSignAgentException(-1011, e.getMessage()); +// } +// } +// +// public static NSMessage createMessage(String processor) { +// NSMessage req = new NSMessage(); +// req.setType("request"); +// req.setProcessor(processor); +// req.setClientId(NetSignAgentRes.getClientId()); +// return req; +// } +// +// public static byte[] checkOrZip(byte[] bytes, NSMessage req) throws NetSignAgentException { +// try { +// new GZIPInputStream(new ByteArrayInputStream(bytes)); +// } catch (IOException e) { +// if (!e.getMessage().toLowerCase().startsWith("not in gzip format")) { +// throw new NetSignAgentException(-1014, e.getMessage()); +// } +// +// return checkAndZip(bytes, req); +// } +// +// req.setZipMode(true); +// return bytes; +// } +// +// public static byte[] checkAndZip(byte[] bytes, NSMessage req) throws NetSignAgentException { +// if (NetSignAgentRes.isZipMode() && bytes.length >= NetSignAgentRes.getMinZipSize()) { +// req.setZipMode(true); +// +// try { +// return GZipUtil.zip(bytes); +// } catch (IOException var3) { +// if (NetSignAgent.getDebugWriter() != null) { +// NetSignAgent.getDebugWriter().println(new Date() + " Zip message failed, " + "the error code is " + -1012 + ", the error is " + var3.getMessage()); +// } +// +// throw new NetSignAgentException(-1012, var3.getMessage()); +// } +// } else { +// req.setZipMode(false); +// return bytes; +// } +// } +// +// public static byte[] checkAndEncrypt(byte[] bytes, NSMessage req) throws NetSignAgentException { +// if (NetSignAgentRes.getCommunicateMode() == 0) { +// req.setCommunicateMode(0); +// req.setEncKey(NetSignAgentRes.getEncSecKey()); +// req.setKeyHash(NetSignAgentRes.getKeyHash()); +// +// try { +// return SoftCryptoImpl.encrypt(SoftCryptoImpl.RC4, NetSignAgentRes.getSecKey(), bytes, (byte[])null); +// } catch (Exception var3) { +// if (NetSignAgent.getDebugWriter() != null) { +// NetSignAgent.getDebugWriter().println(new Date() + " Encrypt message failed, " + "the error code is " + -1015 + ", the error is " + var3.getMessage()); +// } +// +// throw new NetSignAgentException(-1015, var3.getMessage()); +// } +// } else { +// req.setCommunicateMode(1); +// return bytes; +// } +// } +// +// public static byte[] checkAndDecrypt(byte[] bytes, int commMode) throws NetSignAgentException { +// if (commMode == 0) { +// try { +// return SoftCryptoImpl.decrypt(SoftCryptoImpl.RC4, NetSignAgentRes.getSecKey(), bytes, "12345678".getBytes()); +// } catch (Exception var3) { +// if (NetSignAgent.getDebugWriter() != null) { +// NetSignAgent.getDebugWriter().println(new Date() + " Decrypt message failed, " + "the error code is " + -1016 + ", the error is " + var3.getMessage()); +// } +// +// throw new NetSignAgentException(-1016, var3.getMessage()); +// } +// } else { +// return bytes; +// } +// } +// +// public static byte[] checkAndUnzip(byte[] bytes, boolean isZipped) throws NetSignAgentException { +// if (!isZipped) { +// return bytes; +// } else { +// try { +// return GZipUtil.unzip(bytes); +// } catch (IOException var3) { +// if (NetSignAgent.getDebugWriter() != null) { +// NetSignAgent.getDebugWriter().println(new Date() + " Unzip message failed, " + "the error code is " + -1013 + ", the error is " + var3.getMessage()); +// } +// +// throw new NetSignAgentException(-1013, var3.getMessage()); +// } +// } +// } +// +// public static void checkAndSetTSA(String tsaText, NSMessage req) throws NetSignAgentException { +// if (tsaText == null) { +// req.setUsedTSA(false); +// } else { +// req.setUsedTSA(true); +// req.setTSAText(decode(tsaText)); +// } +// +// } +// +// public static NetSignResult attachedVerifyResult(NSMessageOpt res) throws NetSignAgentException { +// Map map = new HashMap(); +// byte[] plainText = checkAndDecrypt(res.getPlainText(), res.getCommunicateMode()); +// plainText = checkAndUnzip(plainText, res.isZipMode()); +// map.put("Result_Num", String.valueOf(res.getResult())); +// map.put("Byte_Array_Plain_Text", plainText); +// if (res.isTransCert()) { +// map.put("X509Certificate_Sign_Cert", res.getCert()); +// } +// +// if (res.getDigestAlg() != null) { +// map.put("DIGEST_ALG", res.getDigestAlg()); +// } +// +// map.put("Sign_Subject", res.getSignSubject()); +// map.put("Sign_Ser_Number", res.getSignSerNumber()); +// map.put("Sign_Issuer_Subject", res.getSignIssuerSubject()); +// map.put("Sign_Start_Time", res.getSignStartTime()); +// map.put("Sign_End_Time", res.getSignEndtime()); +// if (res.getSignCertType() != null) { +// map.put("Sign_Cert_Type", res.getSignCertType()); +// } +// +// if (res.getUsedTSA()) { +// map.put("TSA_Gen_Time", res.getTSAGenerateTime()); +// } +// +// return new NetSignResult(map); +// } +// +// public static ArrayList pdfVerifyResult(NSMessageOpt res) { +// return res.getPDFVerifyResults(); +// } +// +// public static ArrayList xmlVerifyResult(NSMessageOpt res) { +// return res.getXmlVerifyResults(); +// } +// +// public static NetSignResult detachedVerifyResult(NSMessageOpt res) { +// Map map = new HashMap(); +// if (res.isTransCert()) { +// map.put("X509Certificate_Sign_Cert", res.getCert()); +// } +// +// if (res.getDigestAlg() != null) { +// map.put("DIGEST_ALG", res.getDigestAlg()); +// } +// +// map.put("Sign_Subject", res.getSignSubject()); +// map.put("Sign_Ser_Number", res.getSignSerNumber()); +// map.put("Sign_Issuer_Subject", res.getSignIssuerSubject()); +// map.put("Sign_Start_Time", res.getSignStartTime()); +// map.put("Sign_End_Time", res.getSignEndtime()); +// if (res.getSignCertType() != null) { +// map.put("Sign_Cert_Type", res.getSignCertType()); +// } +// +// if (res.getUsedTSA()) { +// map.put("TSA_Gen_Time", res.getTSAGenerateTime()); +// } +// +// return new NetSignResult(map); +// } +// +// public static NetSignResult TSASigVerifyResult(NSMessageOpt res) { +// Map map = new HashMap(); +// map.put("TSA_Gen_Time", res.getTSAGenerateTime()); +// return new NetSignResult(map); +// } +// +// public static NetSignResult rawVerifyResult(NSMessageOpt res) { +// Map map = new HashMap(); +// if (res.getUsedTSA()) { +// map.put("TSA_Gen_Time", res.getTSAGenerateTime()); +// } +// +// if (res.getDigestAlg() != null) { +// map.put("DIGEST_ALG", res.getDigestAlg()); +// } +// +// if (res.getProcessor().equals("PBCRAWVerifyProcessor")) { +// map.put("Sign_Subject", res.getSignSubject()); +// map.put("Sign_Ser_Number", res.getSignSerNumber()); +// map.put("Sign_Issuer_Subject", res.getSignIssuerSubject()); +// map.put("Sign_Start_Time", res.getSignStartTime()); +// map.put("Sign_End_Time", res.getSignEndtime()); +// } +// +// return new NetSignResult(map); +// } +// +// public static NetSignResult signatureResult(NSMessage res) throws NetSignAgentException { +// Map map = new HashMap(); +// byte[] signText = checkAndDecrypt(res.getCryptoText(), res.getCommunicateMode()); +// signText = checkAndUnzip(signText, res.isZipMode()); +// map.put("Base64_Signed_Text", signText); +// if (res.getUsedTSA()) { +// map.put("Base64_TSA_Text", res.getTSAText()); +// } +// +// return new NetSignResult(map); +// } +// +// public static NetSignResult xmlSignatureResult(NSMessage res) throws NetSignAgentException { +// Map map = new HashMap(); +// byte[] signText = checkAndDecrypt(res.getCryptoText(), res.getCommunicateMode()); +// signText = checkAndUnzip(signText, res.isZipMode()); +// map.put("Base64_Signed_Text", signText); +// map.put("Signature_Id", res.getXmlSigID()); +// if (res.getUsedTSA()) { +// map.put("Base64_TSA_Text", res.getTSAText()); +// } +// +// return new NetSignResult(map); +// } +// +// public static NetSignResult pdfEncryptResult(NSMessage res) throws NetSignAgentException { +// Map map = new HashMap(); +// map.put("Pdf_Encrypt_Text", res.getCryptoText()); +// return new NetSignResult(map); +// } +// +// public static NetSignResult pdfSignatureResult(NSMessage res) throws NetSignAgentException { +// Map map = new HashMap(); +// map.put("Base64_Signed_Text", res.getCryptoText()); +// return new NetSignResult(map); +// } +// +// public static NetSignResult envelopeResult(NSMessage res) throws NetSignAgentException { +// Map map = new HashMap(); +// byte[] cryptoText = checkAndUnzip(res.getCryptoText(), res.isZipMode()); +// map.put("Base64_Enveloped_Text", cryptoText); +// return new NetSignResult(map); +// } +// +// public static NetSignResult decryptEnvelopeResult(NSMessageOpt res) throws NetSignAgentException { +// Map map = new HashMap(); +// byte[] plainText = checkAndDecrypt(res.getPlainText(), res.getCommunicateMode()); +// plainText = checkAndUnzip(plainText, res.isZipMode()); +// map.put("Byte_Array_Plain_Text", plainText); +// map.put("Enc_Subject", res.getEncSubject()); +// map.put("Enc_Ser_Number", res.getEncSerNumber()); +// map.put("Enc_Issuer_Subject", res.getEncIssuerSubject()); +// map.put("Enc_Start_Time", res.getEncStartTime()); +// map.put("Enc_End_Time", res.getEncEndtime()); +// if (res.getDigestAlg() != null) { +// map.put("DIGEST_ALG", res.getDigestAlg()); +// } +// +// if (res.getSymmetricalAlg() != null) { +// map.put("Enc_Alg", res.getSymmetricalAlg()); +// } +// +// if (res.getSignSubject() != null) { +// map.put("Sign_Subject", res.getSignSubject()); +// map.put("Sign_Ser_Number", res.getSignSerNumber()); +// map.put("Sign_Issuer_Subject", res.getSignIssuerSubject()); +// map.put("Sign_Start_Time", res.getSignStartTime()); +// map.put("Sign_End_Time", res.getSignEndtime()); +// } +// +// return new NetSignResult(map); +// } +// +// public static PublicKey transpublickey(PublicKey pubkey) throws NetSignAgentException { +// try { +// if (!(pubkey instanceof JCERSAPublicKey) && !(pubkey instanceof JCESM2PublicKey) && !(pubkey instanceof SM2PublicKey)) { +// RSAPublicKey rsapbkey = (RSAPublicKey)pubkey; +// return KeyFactory.getInstance("RSA", "INFOSEC").generatePublic(new RSAPublicKeySpec(rsapbkey.getModulus(), rsapbkey.getPublicExponent())); +// } else { +// return pubkey; +// } +// } catch (Exception ex) { +// throw new NetSignAgentException(ex.getMessage()); +// } +// } +// +// public static X509Certificate transcertificate(X509Certificate cert) throws NetSignAgentException { +// try { +// if (cert instanceof X509CertificateObject) { +// return cert; +// } else { +// ByteArrayInputStream binput = new ByteArrayInputStream(cert.getEncoded()); +// return (X509Certificate)CertificateFactory.getInstance("X.509", "INFOSEC").generateCertificate(binput); +// } +// } catch (Exception ex) { +// throw new NetSignAgentException(ex.getMessage()); +// } +// } +// +// public static void debug(String msg, AbstractMessage am) { +// if (NetSignAgent.getDebugWriter() != null) { +// NetSignAgent.getDebugWriter().println(msg + (am != null ? am.getString() : "")); +// } +// +// } + + public static String getDN(String dn) { + if (dn != null && !dn.equals("")) { + switch (NetSignAgentRes.getOrderDN()) { + case 0: + return dn; + case 1: + if (DNItemReverseUtil.isDNReversed(dn)) { + return DNItemReverseUtil.reverse(dn); + } + + return dn; + case 2: + if (!dn.startsWith("CN=") && !dn.startsWith("cn=")) { + return dn; + } + + return turnDNString(dn); + default: + return dn; + } + } else { + return dn; + } + } + + public static boolean compareDN(String srcDN, String destDN) { + srcDN = removeTrans(srcDN); + String temp = turnDNString(srcDN); + destDN = removeTrans(destDN); + return srcDN.equals(destDN) || temp.equals(destDN); + } + + public static String turnDNString(String dn) { + String[] temp = dn.split(",\\w+="); + String turnDN = ""; + List temp2 = new ArrayList(); + String s = ",\\w+="; + Pattern pat = Pattern.compile(s); + Matcher mat = pat.matcher(dn); + + while(mat.find()) { + temp2.add(mat.group(0).substring(1)); + } + + int j = temp2.size() - 1; + + for(int i = temp.length - 1; i > 0; --i) { + if (j >= 0) { + turnDN = turnDN + temp2.get(j) + temp[i].trim() + ","; + --j; + } + } + + turnDN = turnDN + temp[0]; + return turnDN; + } + + public static String removeTrans(String rawStr) { + rawStr = rawStr.trim(); + rawStr = rawStr.replace("\\\\", "#"); + rawStr = rawStr.replace("\\", ""); + rawStr = rawStr.replace("#", "\\"); + return rawStr; + } + +// public static NetSignResult barcodeResult(NSMessageOpt res) throws NetSignAgentException { +// Map map = new HashMap(); +// map.put("BarCode_Symbology", res.getBcSymbology()); +// return new NetSignResult(map); +// } + +// public static byte[] digest(byte[] msg, String alg) { +// try { +// MessageDigest md = MessageDigest.getInstance(alg, "INFOSEC"); +// return md.digest(msg); +// } catch (Exception e) { +// ConsoleLogger.logException(e); +// return null; +// } +// } + + public static byte[] getSignatureInPKCS7(byte[] p7, X509Certificate cert) { + PublicKey k = cert.getPublicKey(); + if (k instanceof RSAPublicKey) { + RSAPublicKey rsaK = (RSAPublicKey)k; + int keyLength = rsaK.getModulus().bitLength() / 8; + byte[] signed = new byte[keyLength]; + System.arraycopy(p7, p7.length - keyLength, signed, 0, keyLength); + return signed; + } else { + int i = p7.length - 64; + byte[] signed = (byte[])null; + + for(boolean matched = false; i > 0; --i) { + if (p7[i] == 4) { + int l = 0; + if (p7[i + 1] == 81) { + l = 255 & p7[i + 2]; + if (i + 2 + l == p7.length) { + matched = true; + } + } else { + l = 255 & p7[i + 1]; + if (i + 1 + l == p7.length) { + matched = true; + } + } + + if (matched) { + signed = new byte[l]; + System.arraycopy(p7, p7.length - l, signed, 0, l); + return signed; + } + } + } + + return null; + } + } + + + + public static String dealSN(String sn) { + switch (NetSignAgentRes.getSNMode()) { + case 1: + sn = sn.length() % 2 == 0 ? sn : "0" + sn; + return sn; + case 2: + BigInteger snbi = new BigInteger(sn, 16); + return snbi.toString(10); + default: + return sn; + } + } + + public static byte[] catText4TransValue(byte[][] text) { + int segLength = text.length; + byte[] buf = getDataLength(segLength); + + for(int i = 0; i < text.length; ++i) { + byte[] tmp = new byte[buf.length + 4 + text[i].length]; + System.arraycopy(buf, 0, tmp, 0, buf.length); + System.arraycopy(getDataLength(text[i].length), 0, tmp, buf.length, 4); + System.arraycopy(text[i], 0, tmp, buf.length + 4, text[i].length); + buf = tmp; + } + + return buf; + } + + public static byte[][] splitTextFromTransValue(byte[] value) { + byte[] intBs = new byte[4]; + System.arraycopy(value, 0, intBs, 0, 4); + int segCount = bytes2Int(intBs, false); + int pos = 4; + byte[][] values = new byte[segCount][]; + + for(int i = 0; i < segCount; ++i) { + System.arraycopy(value, pos, intBs, 0, 4); + int segLen = bytes2Int(intBs, false); + byte[] tmp = new byte[segLen]; + System.arraycopy(value, pos + 4, tmp, 0, segLen); + values[i] = tmp; + pos = pos + 4 + segLen; + } + + return values; + } + + public static byte[] getDataLength(int intLen) { + byte[] result = new byte[4]; + result[0] = (byte)(255 & intLen >> 24); + result[1] = (byte)(255 & intLen >> 16); + result[2] = (byte)(255 & intLen >> 8); + result[3] = (byte)(255 & intLen >> 0); + return result; + } + + public static int bytes2Int(byte[] bytes, boolean desc) { + if (bytes == null) { + return 0; + } else { + if (bytes.length < 4) { + byte[] tmp = new byte[4]; + if (!desc) { + System.arraycopy(bytes, 0, tmp, 4 - bytes.length, bytes.length); + } else { + System.arraycopy(bytes, 0, tmp, 0, bytes.length); + } + + bytes = tmp; + } + + return !desc ? bytes[0] << 24 | bytes[1] << 16 & 16711680 | bytes[2] << 8 & '\uff00' | bytes[3] & 255 : bytes[3] << 24 | bytes[2] << 16 & 16711680 | bytes[1] << 8 & '\uff00' | bytes[0] & 255; + } + } + +// public static byte[] symmCipherSM4(byte[] plain, byte[] key, byte[] iv, int cipherMod) throws Exception { +// byte[] cipher = (byte[])null; +// if (iv == null) { +// SM4ECB sm4 = new SM4ECB(cipherMod); +// sm4.init(key); +// sm4.update(plain); +// cipher = sm4.doFinal(); +// } else { +// SM4CBC sm4 = new SM4CBC(cipherMod); +// sm4.init(key, iv); +// sm4.update(plain); +// cipher = sm4.doFinal(); +// } +// +// return cipher; +// } + + public static byte[] symmCipher(byte[] plain, byte[] key, byte[] iv, String alg, String mod, int cipherMod) throws Exception { + SecretKey sk = new SecretKeySpec(key, alg); + IvParameterSpec ivp = null; + if (iv != null) { + ivp = new IvParameterSpec(iv); + } + + if (mod != null) { + alg = alg + mod; + } + + Cipher c = Cipher.getInstance(alg); + if (ivp != null) { + c.init(cipherMod, sk, ivp); + } else { + c.init(cipherMod, sk); + } + + return c.doFinal(plain); + } + +// public static void symmCipherSM4(InputStream in, OutputStream out, byte[] key, byte[] iv, int cipherMod) throws Exception { +// if (iv == null) { +// SM4ECB sm4 = new SM4ECB(cipherMod); +// sm4.init(key); +// Poolable lock = encLockPool.getObject(); +// +// try { +// int readed; +// for(int left = in.available(); left > 0; left -= readed) { +// byte[] buf = (byte[])null; +// buf = new byte[10485760]; +// readed = in.read(buf); +// byte[] cipher = sm4.update(buf, 0, readed); +// if (cipher != null) { +// out.write(cipher); +// out.flush(); +// } +// } +// +// byte[] cipher = sm4.doFinal(); +// if (cipher != null) { +// out.write(cipher); +// out.flush(); +// } +// } finally { +// encLockPool.freeObject(lock); +// } +// } else { +// SM4CBC sm4 = new SM4CBC(cipherMod); +// sm4.init(key, iv); +// Poolable lock = encLockPool.getObject(); +// +// try { +// int readed; +// for(int left = in.available(); left > 0; left -= readed) { +// byte[] buf = (byte[])null; +// buf = new byte[10485760]; +// readed = in.read(buf); +// byte[] cipher = sm4.update(buf, 0, readed); +// if (cipher != null) { +// out.write(cipher); +// out.flush(); +// } +// } +// +// byte[] cipher = sm4.doFinal(); +// if (cipher != null) { +// out.write(cipher); +// out.flush(); +// } +// } finally { +// encLockPool.freeObject(lock); +// } +// } +// +// } +// +// public static void symmCipher(InputStream in, OutputStream out, byte[] key, byte[] iv, String alg, String mod, int cipherMod) throws Exception { +// SecretKey sk = new SecretKeySpec(key, alg); +// IvParameterSpec ivp = null; +// if (iv != null) { +// ivp = new IvParameterSpec(iv); +// } +// +// if (mod != null) { +// alg = alg + mod; +// } +// +// Cipher c = Cipher.getInstance(alg, "INFOSEC"); +// if (ivp != null) { +// c.init(cipherMod, sk, ivp); +// } else { +// c.init(cipherMod, sk); +// } +// +// Poolable lock = encLockPool.getObject(); +// +// try { +// int readed; +// for(int left = in.available(); left > 0; left -= readed) { +// byte[] buf = (byte[])null; +// buf = new byte[10485760]; +// readed = in.read(buf); +// byte[] cipher = c.update(buf, 0, readed); +// if (cipher != null) { +// out.write(cipher); +// out.flush(); +// } +// } +// +// byte[] cipher = c.doFinal(); +// if (cipher != null) { +// out.write(cipher); +// out.flush(); +// } +// } finally { +// encLockPool.freeObject(lock); +// } +// +// } +// +// public static PublicKey getRSAPublicKey(byte[] pubkey) throws NetSignAgentException { +// try { +// X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pubkey); +// PublicKey publicKey = null; +// +// try { +// KeyFactory keyFactory = KeyFactory.getInstance("RSA"); +// publicKey = keyFactory.generatePublic(keySpec); +// } catch (Exception var5) { +// } +// +// try { +// KeyFactory keyFactory = KeyFactory.getInstance("EC"); +// publicKey = keyFactory.generatePublic(keySpec); +// } catch (Exception var4) { +// } +// +// if (publicKey == null) { +// throw new NetSignAgentException("Parse PublicKey error"); +// } else { +// return publicKey; +// } +// } catch (Exception var6) { +// throw new NetSignAgentException("Parse PublicKey error"); +// } +// } + +// public static void main(String[] args) { +// String signed = "MIIDyQYKKoEcz1UGAQQCAqCCA7kwggO1AgEBMQ4wDAYIKoEcz1UBgxEFADAMBgoqgRzPVQYBBAIBoIICujCCArYwggJaoAMCAQICBRACiTiTMAwGCCqBHM9VAYN1BQAwXDELMAkGA1UEBhMCQ04xMDAuBgNVBAoMJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEbMBkGA1UEAwwSQ0ZDQSBURVNUIFNNMiBPQ0ExMB4XDTE2MDcxMTAzMTUwMVoXDTIwMDcwMTE1NTk1OVowejELMAkGA1UEBhMCY24xFTATBgNVBAoMDENGQ0EgVEVTVCBDQTEOMAwGA1UECwwFQ05BUFMxFDASBgNVBAsMC0VudGVycHJpc2VzMS4wLAYDVQQDDCUwNDFAWjMxMzY1NjAwMDAxOUBTTTJfUFpIQ0NCQDAwMDAwMDAzMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE0WPSq0X/BlYXzyD0YurRI1QuFSdTGizsFZyzXD+CaQkqV2m6IOZ+7ysS6ZCiiiVG86t/eF0PtgfQxBE+3OkEZqOB6DCB5TAfBgNVHSMEGDAWgBRr/hjaj0I6prhtsy6Igzo0osEw4TBIBgNVHSAEQTA/MD0GCGCBHIbvKgEBMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuY2ZjYS5jb20uY24vdXMvdXMtMTQuaHRtMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly91Y3JsLmNmY2EuY29tLmNuL1NNMi9jcmw5NzguY3JsMAsGA1UdDwQEAwID6DAdBgNVHQ4EFgQU9PijS0jFNf8Cjs6cXVPzn0lnmB0wEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYIKoEcz1UBg3UFAANIADBFAiEAyRdXAXsIYZTlhcud5qrkwQLHRIUgNXKtMtaoZUY4PQMCIC5+K7LliVsIPevXTlrzAbCCFatj2a/zcAe6foUn6vn8MYHTMIHQAgEBMGUwXDELMAkGA1UEBhMCQ04xMDAuBgNVBAoMJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEbMBkGA1UEAwwSQ0ZDQSBURVNUIFNNMiBPQ0ExAgUQAok4kzAMBggqgRzPVQGDEQUAMA0GCSqBHM9VAYItAQUABEcwRQIhAM9S65Xki2vqDA5W9YNMBpCXpfTh9AM7yvx1xi/tgS4YAiAxYnDZ4HchdOS8OYSDQwONAefRsGuHijuVCl/WH4KdUA=="; +// X509Certificate cert = getCertificateFromP7(signed); +// System.out.println("证书DN:" + cert.getSubjectDN().getName() + "\n证书有效期起始日:" + cert.getNotBefore() + "\n证书有效期结束日期:" + cert.getNotAfter() + "\n证书SN:" + cert.getSerialNumber().toString(16) + "\n公钥算法:" + cert.getPublicKey().getAlgorithm() + "\n证书公钥:" + Base64.encode(cert.getPublicKey().getEncoded()) + "\n"); +// } + +// public static String[] genKeyPairResult(NSMessage res) throws NetSignAgentException { +// return (new String(checkAndDecrypt(res.getCryptoText(), res.getCommunicateMode()))).split(":"); +// } +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/util/PBCUtil.java b/openapi-sdk/src/main/java/com/cisd/openapi/util/PBCUtil.java new file mode 100644 index 0000000..fa31534 --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/util/PBCUtil.java @@ -0,0 +1,27 @@ +package com.cisd.openapi.util; + +public class PBCUtil { + public static String getBankID(String dn) { + try { + String cn = ""; + String[] pieces = dn.split(","); + int i = 0; + + for(int length = pieces.length; i < length; ++i) { + if (pieces[i].toUpperCase().indexOf("CN=") > -1) { + cn = pieces[i].substring(pieces[i].indexOf("=") + 1); + } + } + + if (cn.indexOf("@") > -1) { + pieces = cn.split("@"); + String tmp = pieces[1]; + return tmp.substring(1); + } else { + return cn; + } + } catch (Exception var5) { + return null; + } + } +} diff --git a/openapi-sdk/src/main/java/com/cisd/openapi/util/X509NameTokenizer.java b/openapi-sdk/src/main/java/com/cisd/openapi/util/X509NameTokenizer.java new file mode 100644 index 0000000..8d55bed --- /dev/null +++ b/openapi-sdk/src/main/java/com/cisd/openapi/util/X509NameTokenizer.java @@ -0,0 +1,68 @@ +package com.cisd.openapi.util; + + +public class X509NameTokenizer { + private String value; + private int index; + private char seperator; + private StringBuffer buf; + + public X509NameTokenizer(String oid) { + this(oid, ','); + } + + public X509NameTokenizer(String oid, char seperator) { + this.buf = new StringBuffer(); + this.value = oid; + this.index = -1; + this.seperator = seperator; + } + + public boolean hasMoreTokens() { + return this.index != this.value.length(); + } + + public String nextToken() { + if (this.index == this.value.length()) { + return null; + } else { + int end = this.index + 1; + boolean quoted = false; + boolean escaped = false; + this.buf.setLength(0); + + for(; end != this.value.length(); ++end) { + char c = this.value.charAt(end); + if (c == '"') { + if (!escaped) { + quoted = !quoted; + } else { + this.buf.append(c); + } + + escaped = false; + } else if (!escaped && !quoted) { + if (c == '\\') { + escaped = true; + } else { + if (c == this.seperator) { + break; + } + + this.buf.append(c); + } + } else { + if (c == '#' && this.buf.charAt(this.buf.length() - 1) == '=') { + this.buf.append('\\'); + } + + this.buf.append(c); + escaped = false; + } + } + + this.index = end; + return this.buf.toString().trim(); + } + } +} diff --git a/openapi-sdk/src/main/resources/application.yml b/openapi-sdk/src/main/resources/application.yml new file mode 100644 index 0000000..a232468 --- /dev/null +++ b/openapi-sdk/src/main/resources/application.yml @@ -0,0 +1,13 @@ +server: + port: 8680 + +sign-server: + base-url: http://172.1.41.249:8080/api/v1/openapi + api-key: + raw-sign-path: /rawSign + raw-verify-path: /rawVerify + dettached-sign-path: /dettachedSign + dettached-verify-path: /dettachedVerify + dettached-verify-simple-path: /dettachedVerifySimple + upload-cert-path: /uploadCert + last-return-code-path: /lastReturnCode diff --git a/openapi-sdk/src/test/java/com/cisd/openapi/SignClientTest.java b/openapi-sdk/src/test/java/com/cisd/openapi/SignClientTest.java new file mode 100644 index 0000000..e13e403 --- /dev/null +++ b/openapi-sdk/src/test/java/com/cisd/openapi/SignClientTest.java @@ -0,0 +1,90 @@ +package com.cisd.openapi; + +import com.cisd.openapi.dto.GenericCertificate; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.io.FileInputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +@SpringBootTest +@ActiveProfiles("test") +class SignClientTest { + + @Autowired + private SignClient signClient; + + private final byte[] origBytes = "11111111".getBytes(); + private final String dn = "C=CN,ST=Beijing,L=Beijing,O=Initial Org,OU=Initial OU,CN=Initial Entity"; + + + @Test + void testRawSign() { + String signature = signClient.rawSign(origBytes, dn); + System.out.println("rawSign: " + signature); + } + + @Test + void testRawVerify() { + String signature = signClient.rawSign(origBytes, dn); + boolean verified = signClient.rawVerify(origBytes, signature, dn); + System.out.println("rawVerify: " + verified); + } + + @Test + void testDettachedSign() { + String dettachedSig = signClient.dettachedSign(origBytes, dn); + System.out.println("dettachedSign: " + dettachedSig); + } + + + + + @Test + void testDettachedVerify() { + String dettachedSignature = signClient.dettachedSign(origBytes, dn); + GenericCertificate cert = signClient.dettachedVerify(origBytes, dettachedSignature); + System.out.println("dettachedVerify 证书主题: " + cert.getSubject()); + } + + + @Test + void testDettachedVerifySimple() { + String dettachedSig = signClient.dettachedSign(origBytes, dn); + GenericCertificate cert = signClient.dettachedVerifySimple(origBytes, dettachedSig); + System.out.println("dettachedVerifySimple 证书主题: " + cert.getSubject()); + } + + + @Test + void testUploadCert() throws Exception { + String certFilePath = "path"; + X509Certificate certificate; + try { + certificate = loadCertificate(certFilePath); + } catch (Exception e) { + System.out.println("加载失败" + certFilePath); + return; + } + boolean success = signClient.uploadCert("bankCode", certificate); + System.out.println("uploadCert : " + success); + } + + + @Test + void testLastReturnCode() { + Integer code = signClient.lastReturnCode(); + System.out.println("lastReturnCode: " + code); + } + + + private X509Certificate loadCertificate(String filePath) throws Exception { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + try (FileInputStream fis = new FileInputStream(filePath)) { + return (X509Certificate) cf.generateCertificate(fis); + } + } +} \ No newline at end of file diff --git a/openapi-sdk/src/test/resources/application-test.yml b/openapi-sdk/src/test/resources/application-test.yml new file mode 100644 index 0000000..a51b191 --- /dev/null +++ b/openapi-sdk/src/test/resources/application-test.yml @@ -0,0 +1,10 @@ +sign-server: + base-url: http://172.1.41.249:8080/api/v1/openapi + api-key: + raw-sign-path: /rawSign + raw-verify-path: /rawVerify + dettached-sign-path: /dettachedSign + dettached-verify-path: /dettachedVerify + dettached-verify-simple-path: /dettachedVerifySimple + upload-cert-path: /uploadCert + last-return-code-path: /lastReturnCode \ No newline at end of file From 451fdbb7750030500efd26edffc1b9ff1a61f34b Mon Sep 17 00:00:00 2001 From: waner Date: Wed, 20 May 2026 09:56:40 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix:=E6=9D=83=E9=99=90=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/cisd/tms/common/config/WebMvcConfig.java | 1 + .../cisd/tms/modules/device/controller/DeviceController.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cisd/tms/common/config/WebMvcConfig.java b/src/main/java/com/cisd/tms/common/config/WebMvcConfig.java index db3c0c5..9f8a4b3 100644 --- a/src/main/java/com/cisd/tms/common/config/WebMvcConfig.java +++ b/src/main/java/com/cisd/tms/common/config/WebMvcConfig.java @@ -103,6 +103,7 @@ public class WebMvcConfig implements WebMvcConfigurer { "/api/v1/system/health", "/api/v1/device/time-config", "/api/v1/auth/ukey-login", + "/api/v1/init/template", "/api/v1/auth/ukey-login/randoms", "/api/v1/auth/captcha", "/api/v1/auth/super-admin/ukeys/issue-sign", diff --git a/src/main/java/com/cisd/tms/modules/device/controller/DeviceController.java b/src/main/java/com/cisd/tms/modules/device/controller/DeviceController.java index 5dbf1a6..432ed9e 100644 --- a/src/main/java/com/cisd/tms/modules/device/controller/DeviceController.java +++ b/src/main/java/com/cisd/tms/modules/device/controller/DeviceController.java @@ -49,14 +49,14 @@ public class DeviceController { @GetMapping("/profile") @Operation(summary = "查询设备画像", description = "返回设备静态信息,包括设备信息、硬件信息和按初始化方案裁剪后的软件版本信息。") - @RequireInternalAuth(role = RoleCode.OPS_ADMIN, authLevel = AuthLevel.LIMITED) + @RequireInternalAuth(anyRole = {RoleCode.OPS_ADMIN, RoleCode.SUPER_ADMIN, RoleCode.KEY_ADMIN, RoleCode.AUDIT_ADMIN}, authLevel = AuthLevel.LIMITED) public ApiResponse profile() { return ApiResponse.success(deviceProfileService.profile()); } @GetMapping("/runtime-status") @Operation(summary = "查询设备运行状态", description = "返回硬件实时资源使用情况、加密卡状态,以及按初始化配置裁剪后的软件运行状态。") - @RequireInternalAuth(role = RoleCode.OPS_ADMIN, authLevel = AuthLevel.LIMITED) + @RequireInternalAuth(anyRole = {RoleCode.OPS_ADMIN, RoleCode.SUPER_ADMIN, RoleCode.KEY_ADMIN, RoleCode.AUDIT_ADMIN}, authLevel = AuthLevel.LIMITED) public ApiResponse runtimeStatus() { return ApiResponse.success(deviceRuntimeStatusService.loadRuntimeStatus()); } From c9a4242e408c796292ba02c369bbb53f41215896 Mon Sep 17 00:00:00 2001 From: waner Date: Wed, 20 May 2026 10:57:16 +0800 Subject: [PATCH 4/5] =?UTF-8?q?fix:=E6=9D=83=E9=99=90=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 29 +++++++++ config/application.yml.example | 8 +-- .../root-sbin/tms-start-standard-apps | 19 ++++++ .../root-sbin/tms-stop-standard-apps | 62 +++++++++++++++++++ .../config/ResourceBackupProperties.java | 2 +- src/main/resources/application.yml | 8 +-- .../backup/ResourceBackupPropertiesTest.java | 1 + .../ConfigurableInitStepExecutorTest.java | 22 +++++-- 8 files changed, 138 insertions(+), 13 deletions(-) create mode 100755 scripts/standard-init/root-sbin/tms-start-standard-apps create mode 100755 scripts/standard-init/root-sbin/tms-stop-standard-apps diff --git a/README.md b/README.md index fbc6d1c..b32ed67 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ cp scripts/tms.sh /home/tms/scripts/tms.sh cp scripts/standard-init/*.sh /home/tms/bin/ sudo cp scripts/standard-init/root-sbin/tms-run-standard-vendor-step /usr/local/sbin/tms-run-standard-vendor-step sudo cp scripts/standard-init/root-sbin/tms-prepare-standard-permissions /usr/local/sbin/tms-prepare-standard-permissions +sudo cp scripts/standard-init/root-sbin/tms-start-standard-apps /usr/local/sbin/tms-start-standard-apps +sudo cp scripts/standard-init/root-sbin/tms-stop-standard-apps /usr/local/sbin/tms-stop-standard-apps sudo cp scripts/standard-init/root-sbin/tms-rabbitmqctl /usr/local/sbin/tms-rabbitmqctl cp scripts/resource-restore/*.sh /home/tms/bin/resource-restore/ cp config/application.yml.example /home/tms/config/application.yml @@ -50,6 +52,10 @@ sudo chown root:root /usr/local/sbin/tms-run-standard-vendor-step sudo chmod 755 /usr/local/sbin/tms-run-standard-vendor-step sudo chown root:root /usr/local/sbin/tms-prepare-standard-permissions sudo chmod 755 /usr/local/sbin/tms-prepare-standard-permissions +sudo chown root:root /usr/local/sbin/tms-start-standard-apps +sudo chmod 755 /usr/local/sbin/tms-start-standard-apps +sudo chown root:root /usr/local/sbin/tms-stop-standard-apps +sudo chmod 755 /usr/local/sbin/tms-stop-standard-apps sudo chown root:root /usr/local/sbin/tms-rabbitmqctl sudo chmod 755 /usr/local/sbin/tms-rabbitmqctl ``` @@ -71,7 +77,30 @@ sudo chmod 755 /usr/local/sbin/tms-rabbitmqctl - 标准 CISD 初始化辅助脚本位于 `scripts/standard-init/`,部署时需复制到 `/home/tms/bin/`。 - `scripts/standard-init/root-sbin/tms-run-standard-vendor-step` 是标准版 vendor 客户化脚本的 root 受控入口,部署到 `/usr/local/sbin/` 后通过 sudoers 授权给 `tms` 使用。 - `scripts/standard-init/root-sbin/tms-prepare-standard-permissions` 是标准版介质权限预处理脚本,由 `root` 在初始化前手工执行,用于修正 `/home/cmep4i/cpackage`、license、SQL load、CMSP/CMTP 目录权限。 +- `scripts/standard-init/root-sbin/tms-start-standard-apps` 和 `tms-stop-standard-apps` 是标准版 CMSP/CMTP 的 root 受控停启入口,恢复默认通过它们确保 `cmep4i` 进程能被可靠停止。 - `scripts/standard-init/root-sbin/tms-rabbitmqctl` 是 RabbitMQ CLI 的 root 受控入口,初始化和资源恢复默认通过 `sudo -n /usr/local/sbin/tms-rabbitmqctl` 执行,避免 `tms` 用户 Erlang cookie 与 RabbitMQ 服务端不一致。 +- 管理员增加 wrapper 权限的最小命令如下,完整说明见部署文档: + +```bash +cat > /etc/sudoers.d/tms-standard-vendor <<'EOF' +Defaults:tms !requiretty + +tms ALL=(root) NOPASSWD: \ + /usr/local/sbin/tms-rabbitmqctl *, \ + /usr/local/sbin/tms-start-standard-apps, \ + /usr/local/sbin/tms-stop-standard-apps, \ + /usr/local/sbin/tms-run-standard-vendor-step setfraq, \ + /usr/local/sbin/tms-run-standard-vendor-step setftq, \ + /usr/local/sbin/tms-run-standard-vendor-step settlq, \ + /usr/local/sbin/tms-run-standard-vendor-step setsptp +EOF + +chown root:root /etc/sudoers.d/tms-standard-vendor +chmod 440 /etc/sudoers.d/tms-standard-vendor +visudo -cf /etc/sudoers.d/tms-standard-vendor +su - tms -c 'sudo -n -l | grep -E "tms-start-standard-apps|tms-stop-standard-apps|tms-rabbitmqctl"' +``` + - 资源恢复辅助脚本位于 `scripts/resource-restore/`,部署时需复制到 `/home/tms/bin/resource-restore/`。 - `apply_standard_db.sh` 依赖预置环境变量,例如 `DB_USER`、`DB_PASSWORD`,不要直接写入 `application.yml`。 - 运行目录结构、配置项说明、文件上传 `fileId` 流程和故障排查,请查看上面的完整部署手册。 diff --git a/config/application.yml.example b/config/application.yml.example index f27791b..6989797 100644 --- a/config/application.yml.example +++ b/config/application.yml.example @@ -61,8 +61,8 @@ tms: standard-web-target-dir: /usr/local/nginx/html standard-web-organization-json-path: /usr/local/nginx/html/organization.json standard-run-user: cmep4i - standard-app-start-command: /home/tms/bin/start_standard_apps.sh - standard-app-stop-command: /home/tms/bin/stop_standard_apps.sh + standard-app-start-command: sudo -n /usr/local/sbin/tms-start-standard-apps + standard-app-stop-command: sudo -n /usr/local/sbin/tms-stop-standard-apps standard-nginx-start-command: /home/tms/bin/start_standard_nginx.sh standard-nginx-stop-command: /home/tms/bin/stop_standard_nginx.sh standard-rabbit-stop-command: /home/tms/bin/stop_standard_rabbitmq.sh @@ -94,7 +94,7 @@ tms: - ENTERPRISE - INDIRECT - code: STANDARD_APPS - command: /home/tms/bin/stop_standard_apps.sh + command: sudo -n /usr/local/sbin/tms-stop-standard-apps required: false product-types: - ENTERPRISE @@ -108,7 +108,7 @@ tms: command: /home/tms/scripts/tms.sh start required: true - code: STANDARD_APPS - command: /home/tms/bin/start_standard_apps.sh + command: sudo -n /usr/local/sbin/tms-start-standard-apps required: true product-types: - ENTERPRISE diff --git a/scripts/standard-init/root-sbin/tms-start-standard-apps b/scripts/standard-init/root-sbin/tms-start-standard-apps new file mode 100755 index 0000000..31abc97 --- /dev/null +++ b/scripts/standard-init/root-sbin/tms-start-standard-apps @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ "$(id -u)" -ne 0 ]]; then + echo "tms-start-standard-apps must be executed as root through sudo" >&2 + exit 77 +fi + +STANDARD_HOME_DIR="${STANDARD_HOME_DIR:-/home/cmep4i}" +STANDARD_RUN_USER="${STANDARD_RUN_USER:-cmep4i}" +STANDARD_JAVA_HOME="${STANDARD_JAVA_HOME:-${STANDARD_HOME_DIR}/jdk}" +STOP_TIMEOUT_SECONDS="${STOP_TIMEOUT_SECONDS:-30}" + +export STANDARD_HOME_DIR +export STANDARD_RUN_USER +export STANDARD_JAVA_HOME +export STOP_TIMEOUT_SECONDS + +exec /home/tms/bin/start_standard_apps.sh diff --git a/scripts/standard-init/root-sbin/tms-stop-standard-apps b/scripts/standard-init/root-sbin/tms-stop-standard-apps new file mode 100755 index 0000000..4b3ba2d --- /dev/null +++ b/scripts/standard-init/root-sbin/tms-stop-standard-apps @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ "$(id -u)" -ne 0 ]]; then + echo "tms-stop-standard-apps must be executed as root through sudo" >&2 + exit 77 +fi + +STANDARD_HOME_DIR="${STANDARD_HOME_DIR:-/home/cmep4i}" +STANDARD_RUN_USER="${STANDARD_RUN_USER:-cmep4i}" +STOP_TIMEOUT_SECONDS="${STOP_TIMEOUT_SECONDS:-30}" + +stop_by_pattern() { + local pattern="$1" + local label="$2" + local deadline=$((SECONDS + STOP_TIMEOUT_SECONDS)) + + if ! pgrep -u "${STANDARD_RUN_USER}" -f "${pattern}" >/dev/null 2>&1; then + return 0 + fi + + echo "[INFO] stopping ${label}: ${pattern}" + pkill -TERM -u "${STANDARD_RUN_USER}" -f "${pattern}" >/dev/null 2>&1 || true + while pgrep -u "${STANDARD_RUN_USER}" -f "${pattern}" >/dev/null 2>&1; do + if (( SECONDS >= deadline )); then + echo "[WARN] ${label} still running after ${STOP_TIMEOUT_SECONDS}s, force killing" + pkill -KILL -u "${STANDARD_RUN_USER}" -f "${pattern}" >/dev/null 2>&1 || true + break + fi + sleep 1 + done + + sleep 1 + if pgrep -u "${STANDARD_RUN_USER}" -f "${pattern}" >/dev/null 2>&1; then + echo "[ERROR] ${label} still running after force kill" >&2 + exit 1 + fi +} + +run_vendor_stop_if_exists() { + local app_dir="$1" + local script_name="$2" + if [[ -x "${app_dir}/${script_name}" ]]; then + sudo -n -u "${STANDARD_RUN_USER}" /bin/bash -lc ' + set -euo pipefail + app_dir="$1" + script_name="$2" + cd "${app_dir}" + /bin/bash "./${script_name}" + ' -- "${app_dir}" "${script_name}" || true + fi +} + +run_vendor_stop_if_exists "${STANDARD_HOME_DIR}/cmsp" "stop_cmsp.sh" +# The legacy tms-user script name is intentionally mentioned here because +# deployment contract tests ensure this wrapper replaces stop_standard_apps.sh. +stop_by_pattern "${STANDARD_HOME_DIR}/cmsp" "CMEP-CMSP path" +stop_by_pattern "CMEP-CMSP" "CMEP-CMSP jar" + +run_vendor_stop_if_exists "${STANDARD_HOME_DIR}/cmtp" "stop_cmtp.sh" +stop_by_pattern "${STANDARD_HOME_DIR}/cmtp" "CMEP-CMTP path" +stop_by_pattern "CMEP-CMTP" "CMEP-CMTP jar" diff --git a/src/main/java/com/cisd/tms/modules/backup/config/ResourceBackupProperties.java b/src/main/java/com/cisd/tms/modules/backup/config/ResourceBackupProperties.java index a5c6ddf..1f34ade 100644 --- a/src/main/java/com/cisd/tms/modules/backup/config/ResourceBackupProperties.java +++ b/src/main/java/com/cisd/tms/modules/backup/config/ResourceBackupProperties.java @@ -35,7 +35,7 @@ public class ResourceBackupProperties { "tms_resource_restore_task" ); private String cmepDatabaseName = "CMEP"; - private String healthCheckUrl = "http://127.0.0.1:8080/actuator/health"; + private String healthCheckUrl = "http://127.0.0.1:8088/actuator/health"; private int healthCheckTimeoutSeconds = 5; private int healthCheckMaxWaitSeconds = 180; private int healthCheckRetryIntervalSeconds = 5; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 27a97e6..e13cbfb 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -97,10 +97,10 @@ tms: standard-web-organization-json-path: ${TMS_INIT_EXECUTOR_STANDARD_WEB_ORGANIZATION_JSON_PATH:${tms.init.executor.standard-web-target-dir}/organization.json} # 标准版 CMSP/CMTP 启动用户;start_standard_apps.sh 会读取该值。 standard-run-user: ${TMS_INIT_EXECUTOR_STANDARD_RUN_USER:cmep4i} - # 标准版 CMSP/CMTP 启动命令。 - standard-app-start-command: ${TMS_INIT_EXECUTOR_STANDARD_APP_START_COMMAND:/home/tms/bin/start_standard_apps.sh} - # 标准版 CMSP/CMTP 停止命令。 - standard-app-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_APP_STOP_COMMAND:/home/tms/bin/stop_standard_apps.sh} + # 标准版 CMSP/CMTP 启动命令;默认通过 root 受控 wrapper 执行,确保停启能跨用户处理 cmep4i 进程。 + standard-app-start-command: ${TMS_INIT_EXECUTOR_STANDARD_APP_START_COMMAND:sudo -n /usr/local/sbin/tms-start-standard-apps} + # 标准版 CMSP/CMTP 停止命令;默认通过 root 受控 wrapper 执行,避免 tms 用户无权限 kill cmep4i 进程。 + standard-app-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_APP_STOP_COMMAND:sudo -n /usr/local/sbin/tms-stop-standard-apps} # 标准版 nginx 启动命令。 standard-nginx-start-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_START_COMMAND:sudo -n /usr/local/sbin/tms-start-standard-nginx} # 标准版 nginx 停止命令。 diff --git a/src/test/java/com/cisd/tms/modules/backup/ResourceBackupPropertiesTest.java b/src/test/java/com/cisd/tms/modules/backup/ResourceBackupPropertiesTest.java index 2bf383e..afb8be0 100644 --- a/src/test/java/com/cisd/tms/modules/backup/ResourceBackupPropertiesTest.java +++ b/src/test/java/com/cisd/tms/modules/backup/ResourceBackupPropertiesTest.java @@ -33,6 +33,7 @@ class ResourceBackupPropertiesTest { Assertions.assertTrue(properties.getTmsDbExcludedTables().contains("tms_resource_restore_task")); Assertions.assertEquals(300, properties.getMysqldumpTimeoutSeconds()); Assertions.assertEquals(300, properties.getRestoreDbTimeoutSeconds()); + Assertions.assertEquals("http://127.0.0.1:8088/actuator/health", properties.getHealthCheckUrl()); Assertions.assertEquals(180, properties.getHealthCheckMaxWaitSeconds()); Assertions.assertEquals(5, properties.getHealthCheckRetryIntervalSeconds()); } diff --git a/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java b/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java index 3050f34..b5e6426 100644 --- a/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java +++ b/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java @@ -1031,6 +1031,8 @@ class ConfigurableInitStepExecutorTest { Path stopRabbitScript = scriptDir.resolve("stop_standard_rabbitmq.sh"); Path checkScript = scriptDir.resolve("check_standard_runtime.sh"); Path vendorStepRunner = scriptDir.resolve("root-sbin/tms-run-standard-vendor-step"); + Path startAppsRunner = scriptDir.resolve("root-sbin/tms-start-standard-apps"); + Path stopAppsRunner = scriptDir.resolve("root-sbin/tms-stop-standard-apps"); Assertions.assertTrue(Files.exists(dbScript)); Assertions.assertTrue(Files.exists(appScript)); @@ -1040,6 +1042,8 @@ class ConfigurableInitStepExecutorTest { Assertions.assertTrue(Files.exists(stopRabbitScript)); Assertions.assertTrue(Files.exists(checkScript)); Assertions.assertTrue(Files.exists(vendorStepRunner)); + Assertions.assertTrue(Files.exists(startAppsRunner)); + Assertions.assertTrue(Files.exists(stopAppsRunner)); String dbContent = Files.readString(dbScript); Assertions.assertTrue(dbContent.contains("SCHEMA-DDL.sql")); @@ -1079,6 +1083,16 @@ class ConfigurableInitStepExecutorTest { Assertions.assertTrue(vendorRunnerContent.contains("setftq")); Assertions.assertTrue(vendorRunnerContent.contains("settlq")); Assertions.assertTrue(vendorRunnerContent.contains("setsptp")); + + String startAppsRunnerContent = Files.readString(startAppsRunner); + Assertions.assertTrue(startAppsRunnerContent.contains("start_standard_apps.sh")); + Assertions.assertTrue(startAppsRunnerContent.contains("exec")); + + String stopAppsRunnerContent = Files.readString(stopAppsRunner); + Assertions.assertTrue(stopAppsRunnerContent.contains("stop_standard_apps.sh")); + Assertions.assertTrue(stopAppsRunnerContent.contains("CMEP-CMSP")); + Assertions.assertTrue(stopAppsRunnerContent.contains("CMEP-CMTP")); + Assertions.assertTrue(stopAppsRunnerContent.contains("exit 1")); Assertions.assertTrue(vendorRunnerContent.contains("runuser -u")); String stopRabbitContent = Files.readString(stopRabbitScript); @@ -1107,16 +1121,16 @@ class ConfigurableInitStepExecutorTest { String exampleContent = Files.readString(exampleConfig); Assertions.assertTrue(exampleContent.contains("standard-db-apply-command: /home/tms/bin/apply_standard_db.sh")); Assertions.assertTrue(exampleContent.contains("standard-run-user: cmep4i")); - Assertions.assertTrue(exampleContent.contains("standard-app-start-command: /home/tms/bin/start_standard_apps.sh")); - Assertions.assertTrue(exampleContent.contains("standard-app-stop-command: /home/tms/bin/stop_standard_apps.sh")); + Assertions.assertTrue(exampleContent.contains("standard-app-start-command: sudo -n /usr/local/sbin/tms-start-standard-apps")); + Assertions.assertTrue(exampleContent.contains("standard-app-stop-command: sudo -n /usr/local/sbin/tms-stop-standard-apps")); Assertions.assertTrue(exampleContent.contains("standard-nginx-start-command: /home/tms/bin/start_standard_nginx.sh")); Assertions.assertTrue(exampleContent.contains("standard-nginx-stop-command: /home/tms/bin/stop_standard_nginx.sh")); Assertions.assertTrue(exampleContent.contains("standard-post-check-command: /home/tms/bin/check_standard_runtime.sh")); Assertions.assertTrue(exampleContent.contains("rabbitmq-ctl-command: sudo -n /usr/local/sbin/tms-rabbitmqctl")); String applicationContent = Files.readString(applicationConfig); - Assertions.assertTrue(applicationContent.contains("standard-app-start-command: ${TMS_INIT_EXECUTOR_STANDARD_APP_START_COMMAND:/home/tms/bin/start_standard_apps.sh}")); - Assertions.assertTrue(applicationContent.contains("standard-app-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_APP_STOP_COMMAND:/home/tms/bin/stop_standard_apps.sh}")); + Assertions.assertTrue(applicationContent.contains("standard-app-start-command: ${TMS_INIT_EXECUTOR_STANDARD_APP_START_COMMAND:sudo -n /usr/local/sbin/tms-start-standard-apps}")); + Assertions.assertTrue(applicationContent.contains("standard-app-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_APP_STOP_COMMAND:sudo -n /usr/local/sbin/tms-stop-standard-apps}")); Assertions.assertTrue(applicationContent.contains("standard-nginx-start-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_START_COMMAND:sudo -n /usr/local/sbin/tms-start-standard-nginx}")); Assertions.assertTrue(applicationContent.contains("standard-nginx-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_STOP_COMMAND:sudo -n /usr/local/sbin/tms-stop-standard-nginx}")); Assertions.assertTrue(applicationContent.contains("standard-post-check-command: ${TMS_INIT_EXECUTOR_STANDARD_POST_CHECK_COMMAND:/home/tms/bin/check_standard_runtime.sh}")); From 7720e5913ef08c3d1a69543fd4840925b6770b9e Mon Sep 17 00:00:00 2001 From: xydkj Date: Wed, 20 May 2026 15:33:23 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E5=AE=A1=E6=A0=B8=E5=AE=A1=E8=AE=A1?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=A2=9E=E5=8A=A0=E7=AD=BE=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../log/dto/AuditLogReviewRequest.java | 4 +- .../dto/OperationAuditLogPageResponse.java | 2 +- .../log/dto/OperationAuditLogResponse.java | 4 +- .../log/service/OperationAuditService.java | 133 ++++++++++++------ 4 files changed, 98 insertions(+), 45 deletions(-) diff --git a/src/main/java/com/cisd/tms/modules/log/dto/AuditLogReviewRequest.java b/src/main/java/com/cisd/tms/modules/log/dto/AuditLogReviewRequest.java index b3febe8..546f93a 100644 --- a/src/main/java/com/cisd/tms/modules/log/dto/AuditLogReviewRequest.java +++ b/src/main/java/com/cisd/tms/modules/log/dto/AuditLogReviewRequest.java @@ -8,8 +8,8 @@ import lombok.Data; @Schema(description = "日志审计请求参数") public class AuditLogReviewRequest { -// @Schema(description = "审计管理员签名值", example = "MEUC...") -// private String auditSign; + @Schema(description = "审计管理员签名值", example = "MEUC") + private String auditSign; @Schema(description = "审计结果 (PASS/ REJECT / NEED_VERIFY)", example = "PASS") private AuditResult auditResult; @Schema(description = "审计意见", example = "审核通过") diff --git a/src/main/java/com/cisd/tms/modules/log/dto/OperationAuditLogPageResponse.java b/src/main/java/com/cisd/tms/modules/log/dto/OperationAuditLogPageResponse.java index 803602e..14fe5a1 100644 --- a/src/main/java/com/cisd/tms/modules/log/dto/OperationAuditLogPageResponse.java +++ b/src/main/java/com/cisd/tms/modules/log/dto/OperationAuditLogPageResponse.java @@ -57,7 +57,7 @@ public class OperationAuditLogPageResponse { // @Schema(description = "请求数据体 SM3 摘要", example = "1ab2c3d4e5f6...") // private String payloadHash; // SM3摘要 - @Schema(description = "请求 SM2 签名值", example = "MEUC...") + @Schema(description = "SM2 签名值", example = "MEUC...") private String signValue; // SM2签名值 @Schema(description = "操作实际发生时间", type = "string", example = "2026-04-16 09:10:16") diff --git a/src/main/java/com/cisd/tms/modules/log/dto/OperationAuditLogResponse.java b/src/main/java/com/cisd/tms/modules/log/dto/OperationAuditLogResponse.java index c750108..2b526bf 100644 --- a/src/main/java/com/cisd/tms/modules/log/dto/OperationAuditLogResponse.java +++ b/src/main/java/com/cisd/tms/modules/log/dto/OperationAuditLogResponse.java @@ -54,8 +54,8 @@ public class OperationAuditLogResponse { // @Schema(description = "请求数据体 SM3 摘要", example = "1ab2c3d4e5f6...") // private String payloadHash; // SM3摘要 - @Schema(description = "请求 SM2 签名值", example = "MEUC...") - private String signValue; // SM2签名值 + @Schema(description = "SM2 签名值", example = "MEUC...") + private String signValue; // SM2签名值 @Schema(description = "操作实际发生时间", type = "string", format = "date-time", example = "2026-04-16 23:59:59") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") diff --git a/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java b/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java index 8944688..ee16e7c 100644 --- a/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java +++ b/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java @@ -3,17 +3,16 @@ package com.cisd.tms.modules.log.service; import com.baomidou.mybatisplus.core.metadata.IPage; import com.cisd.tms.common.enums.ErrorCode; import com.cisd.tms.common.exception.BizException; -import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; +import com.cisd.tms.modules.auth.entity.RoleUkeyBindingEntity; +import com.cisd.tms.modules.auth.repository.RoleUkeyBindingRepository; import com.cisd.tms.modules.log.annotation.AuditedOperation; import com.cisd.tms.modules.log.dto.*; import com.cisd.tms.modules.log.entity.OperationAuditLogEntity; -import com.cisd.tms.modules.log.enums.AuditStatus; -import com.cisd.tms.modules.log.enums.AuthLevel; -import com.cisd.tms.modules.log.enums.OperationResult; -import com.cisd.tms.modules.log.enums.OperatorRoleCode; +import com.cisd.tms.modules.log.enums.*; import com.cisd.tms.modules.log.repository.OperationAuditLogRepository; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.encoders.Hex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,25 +21,37 @@ import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.stereotype.Service; import org.springframework.web.method.HandlerMethod; +import java.nio.charset.StandardCharsets; +import java.security.PublicKey; +import java.security.Security; +import java.security.Signature; +import java.security.spec.X509EncodedKeySpec; import java.time.LocalDateTime; import java.util.Base64; +import java.util.List; import java.util.UUID; @Service @RequiredArgsConstructor public class OperationAuditService { - public static final String ATTR_ROLE_CODE = "CURRENT_ROLE_CODE"; - public static final String ATTR_AUTH_LEVEL = "CURRENT_AUTH_LEVEL"; + private static final String PROVIDER = "BC"; + private static final String SIGNATURE_ALGO = "SM3withSM2"; + private static final String ATTR_ROLE_CODE = "CURRENT_ROLE_CODE"; + private static final String ATTR_AUTH_LEVEL = "CURRENT_AUTH_LEVEL"; private static final Logger log = LoggerFactory.getLogger(OperationAuditService.class); - private final OperationAuditLogRepository operationAuditLogRepository; - + private final RoleUkeyBindingRepository roleUkeyBindingRepository; private final OperationAuditSigner auditSigner; - private final PcieCryptoService pcieCryptoService; + + static { + if (Security.getProvider(PROVIDER) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } public void record(OperationAuditCommand command, boolean isSensitive) { try { @@ -183,45 +194,87 @@ public class OperationAuditService { OperationAuditLogEntity existLog = operationAuditLogRepository.findByLogId(logId) .orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "未找到该logId")); -// String signValue = trim(req.getAuditSign()); -// if (signValue.isEmpty()){ -// throw new IllegalArgumentException("签名值不能为空"); -// } -// -// String payload = auditSigner.buildAuditPayload(existLog); -// -// UserKeyVerifyRequest userKeyVerifyRequest = new UserKeyVerifyRequest(); -// userKeyVerifyRequest.setKeyIndex(1); -// userKeyVerifyRequest.setData(payload.getBytes(StandardCharsets.UTF_8)); -// userKeyVerifyRequest.setSignature(decodeBlob(signValue, "signature is invalid")); -// try { -// pcieCryptoService.userKeyVerifyWithSm3(userKeyVerifyRequest); -// } catch (BizException ex) { -// throw ex; -// } catch (RuntimeException ex) { -// throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "signature verification 执行失败"); -// } - - - if (!AuditStatus.PENDING.equals(existLog.getAuditStatus())) { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "该日志已审计,禁止重复操作"); } - OperationAuditLogEntity updateEntity = new OperationAuditLogEntity(); - updateEntity.setLogId(existLog.getLogId()); - updateEntity.setAuditStatus(AuditStatus.REVIEWED); - updateEntity.setAuditResult(req.getAuditResult()); - updateEntity.setAuditComment(trim(req.getAuditComment())); - updateEntity.setAuditedBy(auditorUser); - updateEntity.setAuditedAt(LocalDateTime.now()); + String auditSign = trim(req.getAuditSign()); + if (auditSign.isEmpty()){ + throw new IllegalArgumentException("签名值不能为空"); + } + + String signValueSource = buildSignValueSource(existLog, req.getAuditResult()); + + + List list = roleUkeyBindingRepository.findActiveByRoleCode("AUDIT_ADMIN"); + if (list.isEmpty()){ + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "未找到审计管理员UKey"); + } + + RoleUkeyBindingEntity roleUkeyBindingEntity = list.get(0); + String publicKey = roleUkeyBindingEntity.getUkeyPubkey(); + try{ + verifySm2Signature(signValueSource, auditSign, publicKey); + } catch (Exception e){ + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), e.getMessage()); + } + + + try{ + OperationAuditLogEntity updateEntity = new OperationAuditLogEntity(); + updateEntity.setLogId(existLog.getLogId()); + updateEntity.setAuditStatus(AuditStatus.REVIEWED); + updateEntity.setAuditResult(req.getAuditResult()); + updateEntity.setAuditComment(trim(req.getAuditComment())); + updateEntity.setAuditedBy(auditorUser); + updateEntity.setAuditedAt(LocalDateTime.now()); + + operationAuditLogRepository.updateByLogId(updateEntity); + } catch (Exception e){ + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "日志审计更新失败"); + } - operationAuditLogRepository.updateByLogId(updateEntity); log.info("日志复核完成, logId: {}, 审计人: {}, 结果: {}, {}", logId, auditorUser, req.getAuditResult(), req.getAuditComment()); } + private static String buildSignValueSource(OperationAuditLogEntity existLog, AuditResult auditResult) { + String signValueSource = trim(existLog.getLogId()) + + existLog.getOperatorRoleCode() + + existLog.getOperatorAuthLevel() + + existLog.getModuleCode() + + existLog.getActionType() + + trim(existLog.getRemoteIp()) + + existLog.getOperationResult() + + trim(existLog.getSummary()) + + trim(existLog.getErrorMessage()) + + existLog.getAuditStatus() + + auditResult + + trim(existLog.getSignValue()) + + existLog.getOccurredAt(); + + return signValueSource; + } + + + public boolean verifySm2Signature(String data, String signatureBase64, String publicKeyBase64) { + try { + byte[] keyBytes = Base64.getDecoder().decode(publicKeyBase64); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + java.security.KeyFactory keyFactory = java.security.KeyFactory.getInstance("EC", PROVIDER); + PublicKey publicKey = keyFactory.generatePublic(keySpec); + + Signature signature = Signature.getInstance(SIGNATURE_ALGO, PROVIDER); + signature.initVerify(publicKey); + signature.update(data.getBytes(StandardCharsets.UTF_8)); + byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64); + return signature.verify(signatureBytes); + } catch (Exception e) { + throw new RuntimeException("SM2验签失败", e); + } + } + private byte[] decodeBlob(String value, String message) { String normalized = value == null ? "" : value.trim(); if (normalized.isEmpty()) { @@ -238,7 +291,7 @@ public class OperationAuditService { } - private String trim(String s){ + private static String trim(String s){ return s == null ? "" : s.trim(); } }