ukey-tools
This commit is contained in:
@@ -0,0 +1,66 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.5.11</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.cisd</groupId>
|
||||||
|
<artifactId>ukey-tool</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.java.dev.jna</groupId>
|
||||||
|
<artifactId>jna</artifactId>
|
||||||
|
<version>5.14.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk18on</artifactId>
|
||||||
|
<version>1.81</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>cisd-ukey-tool</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,15 @@
|
|||||||
|
package com.cisd.ukey;
|
||||||
|
|
||||||
|
import com.cisd.ukey.config.SkfProperties;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableConfigurationProperties(SkfProperties.class)
|
||||||
|
public class UkeyToolApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(UkeyToolApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.cisd.ukey.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ConfigurationProperties(prefix = "ukey.skf")
|
||||||
|
public class SkfProperties {
|
||||||
|
|
||||||
|
private String vendorLibName = "SKF.dll";
|
||||||
|
private String libDir = "skflib";
|
||||||
|
private String applicationName = "cisd_app";
|
||||||
|
private String containerName = "cisd_container";
|
||||||
|
private String initAdminPin = "12345678";
|
||||||
|
private String initUserPin = "12345678";
|
||||||
|
private int adminPinRetryCount = 10;
|
||||||
|
private int userPinRetryCount = 10;
|
||||||
|
private int createFileRights = 0x000000FF;
|
||||||
|
private int signKeyAlgId = 0x00020100;
|
||||||
|
private String deviceAuthKey = "1234567812345678";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.cisd.ukey.controller;
|
||||||
|
|
||||||
|
public class ApiResponse<T> {
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
private T data;
|
||||||
|
private String msg;
|
||||||
|
|
||||||
|
public ApiResponse() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse(int code, T data, String msg) {
|
||||||
|
this.code = code;
|
||||||
|
this.data = data;
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> success(T data) {
|
||||||
|
return new ApiResponse<>(0, data, "success");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ApiResponse<T> fail(int code, String msg) {
|
||||||
|
int actualCode = code == 0 ? -1 : code;
|
||||||
|
return new ApiResponse<>(actualCode, null, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCode(int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(T data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMsg() {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMsg(String msg) {
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.cisd.ukey.controller;
|
||||||
|
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.http.server.ServerHttpRequest;
|
||||||
|
import org.springframework.http.server.ServerHttpResponse;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
|
||||||
|
Class<? extends HttpMessageConverter<?>> selectedConverterType,
|
||||||
|
ServerHttpRequest request, ServerHttpResponse response) {
|
||||||
|
if (body instanceof ApiResponse<?>) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
return ApiResponse.success(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package com.cisd.ukey.controller;
|
||||||
|
|
||||||
|
import com.cisd.ukey.service.SkfService;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/skf")
|
||||||
|
public class SkfController {
|
||||||
|
|
||||||
|
private final SkfService skfService;
|
||||||
|
|
||||||
|
public SkfController(SkfService skfService) {
|
||||||
|
this.skfService = skfService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/devices")
|
||||||
|
public List<SkfService.DeviceInfo> listDevices(@RequestParam(defaultValue = "true") boolean presentOnly) {
|
||||||
|
return skfService.listDevices(presentOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/pin/verify")
|
||||||
|
public SkfService.PinVerifyResult verifyPin(@RequestBody PinVerifyRequest request) {
|
||||||
|
return skfService.verifyPin(request.deviceName(), request.pinType(), request.pin());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/pin/change")
|
||||||
|
public SkfService.PinVerifyResult changePin(@RequestBody ChangePinRequest request) {
|
||||||
|
return skfService.changePin(request.deviceName(), request.pinType(), request.oldPin(), request.newPin());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/pin/reset")
|
||||||
|
public SkfService.PinVerifyResult resetPin(@RequestBody ResetPinRequest request) {
|
||||||
|
return skfService.unblockPin(request.deviceName(), request.adminPin(), request.newUserPin());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/file/list")
|
||||||
|
public List<SkfService.FileInfo> listFiles(@RequestBody DeviceRequest request) {
|
||||||
|
return skfService.listFiles(request.deviceName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/file/read")
|
||||||
|
public SkfService.FileContent readFile(@RequestBody ReadFileRequest request) {
|
||||||
|
return skfService.readFile(request.deviceName(), request.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/file/write")
|
||||||
|
public SkfService.FileContent writeFile(@RequestBody WriteFileRequest request) {
|
||||||
|
return skfService.writeFile(request.deviceName(), request.fileName(), request.content());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/file/delete")
|
||||||
|
public void deleteFile(@RequestBody DeleteFileRequest request) {
|
||||||
|
skfService.deleteFile(request.deviceName(), request.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/app/delete")
|
||||||
|
public void deleteApplication(@RequestBody DeviceRequest request) {
|
||||||
|
skfService.deleteApplication(request.deviceName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/ecc/public-key/export")
|
||||||
|
public SkfService.EccPublicKeyResult exportPublicKey(@RequestBody DeviceRequest request,
|
||||||
|
@RequestParam(defaultValue = "true") boolean signFlag) {
|
||||||
|
return skfService.exportEccPublicKey(request.deviceName(), signFlag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/ecc/sign")
|
||||||
|
public SkfService.EccSignatureResult sign(@RequestBody SignRequest request) {
|
||||||
|
return skfService.eccSign(request.deviceName(), request.plainText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/ecc/verify")
|
||||||
|
public SkfService.VerifyResult verify(@RequestBody VerifyRequest request) {
|
||||||
|
return skfService.eccVerify(request.deviceName(), request.publicKey(), request.plainText(), request.signatureBase64());
|
||||||
|
}
|
||||||
|
|
||||||
|
public record DeviceRequest(String deviceName) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record PinVerifyRequest(String deviceName, int pinType, String pin) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ChangePinRequest(String deviceName, int pinType, String oldPin, String newPin) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ResetPinRequest(String deviceName, String adminPin, String newUserPin) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ReadFileRequest(String deviceName, String fileName) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record WriteFileRequest(String deviceName, String fileName, String content) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record DeleteFileRequest(String deviceName, String fileName) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record SignRequest(String deviceName, String plainText) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record VerifyRequest(String deviceName, String publicKey, String plainText, String signatureBase64) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.cisd.ukey.controller;
|
||||||
|
|
||||||
|
import com.cisd.ukey.exception.SkfErrorCodeMessageResolver;
|
||||||
|
import com.cisd.ukey.exception.SkfException;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class SkfExceptionHandler {
|
||||||
|
|
||||||
|
@ExceptionHandler(SkfException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public ApiResponse<Void> handleSkfException(SkfException ex) {
|
||||||
|
String zhMessage = SkfErrorCodeMessageResolver.resolve(ex.getCode());
|
||||||
|
String message = zhMessage == null ? ex.getMessage() : ex.getMessage() + "(" + zhMessage + ")";
|
||||||
|
return ApiResponse.fail(ex.getCode(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
public ApiResponse<Void> handleException(Exception ex) {
|
||||||
|
return ApiResponse.fail(-1, ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.cisd.ukey.exception;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class SkfErrorCodeMessageResolver {
|
||||||
|
|
||||||
|
private static final Map<Integer, String> CODE_MESSAGE_MAP = Map.ofEntries(
|
||||||
|
Map.entry(0x00000000, "成功"),
|
||||||
|
Map.entry(0x0A000001, "失败"),
|
||||||
|
Map.entry(0x0A000002, "异常错误"),
|
||||||
|
Map.entry(0x0A000003, "不支持的服务"),
|
||||||
|
Map.entry(0x0A000004, "文件操作错误"),
|
||||||
|
Map.entry(0x0A000005, "无效的句柄"),
|
||||||
|
Map.entry(0x0A000006, "无效的参数"),
|
||||||
|
Map.entry(0x0A000007, "读文件错误"),
|
||||||
|
Map.entry(0x0A000008, "写文件错误"),
|
||||||
|
Map.entry(0x0A000009, "名称长度错误"),
|
||||||
|
Map.entry(0x0A00000A, "密钥用途错误"),
|
||||||
|
Map.entry(0x0A00000B, "模的长度错误"),
|
||||||
|
Map.entry(0x0A00000C, "未初始化"),
|
||||||
|
Map.entry(0x0A00000D, "对象错误"),
|
||||||
|
Map.entry(0x0A00000E, "内存错误"),
|
||||||
|
Map.entry(0x0A00000F, "超时"),
|
||||||
|
Map.entry(0x0A000010, "输入数据长度错误"),
|
||||||
|
Map.entry(0x0A000011, "输入数据错误"),
|
||||||
|
Map.entry(0x0A000012, "生成随机数错误"),
|
||||||
|
Map.entry(0x0A000013, "HASH对象错误"),
|
||||||
|
Map.entry(0x0A000014, "HASH运算错误"),
|
||||||
|
Map.entry(0x0A000015, "产生RSA密钥错误"),
|
||||||
|
Map.entry(0x0A000016, "RSA密钥模长错误"),
|
||||||
|
Map.entry(0x0A000017, "CSP服务导入公钥错误"),
|
||||||
|
Map.entry(0x0A000018, "RSA加密错误"),
|
||||||
|
Map.entry(0x0A000019, "RSA解密错误"),
|
||||||
|
Map.entry(0x0A00001A, "HASH值不相等"),
|
||||||
|
Map.entry(0x0A00001B, "密钥未发现"),
|
||||||
|
Map.entry(0x0A00001C, "证书未发现"),
|
||||||
|
Map.entry(0x0A00001D, "对象未导出"),
|
||||||
|
Map.entry(0x0A00001E, "解密填充错误"),
|
||||||
|
Map.entry(0x0A00001F, "MAC长度错误"),
|
||||||
|
Map.entry(0x0A000020, "缓冲区不足"),
|
||||||
|
Map.entry(0x0A000021, "密钥类型错误"),
|
||||||
|
Map.entry(0x0A000022, "无事件错误"),
|
||||||
|
Map.entry(0x0A000023, "设备已移除"),
|
||||||
|
Map.entry(0x0A000024, "PIN不正确"),
|
||||||
|
Map.entry(0x0A000025, "PIN被锁死"),
|
||||||
|
Map.entry(0x0A000026, "PIN无效"),
|
||||||
|
Map.entry(0x0A000027, "PIN长度错误"),
|
||||||
|
Map.entry(0x0A000028, "用户已经登录"),
|
||||||
|
Map.entry(0x0A000029, "没有初始化用户口令"),
|
||||||
|
Map.entry(0x0A00002A, "PIN类型错误"),
|
||||||
|
Map.entry(0x0A00002B, "应用名称无效"),
|
||||||
|
Map.entry(0x0A00002C, "应用已经存在"),
|
||||||
|
Map.entry(0x0A00002D, "用户没有登录"),
|
||||||
|
Map.entry(0x0A00002E, "应用不存在"),
|
||||||
|
Map.entry(0x0A00002F, "文件已经存在"),
|
||||||
|
Map.entry(0x0A000030, "空间不足"),
|
||||||
|
Map.entry(0x0A000031, "文件不存在"),
|
||||||
|
Map.entry(0x0A000032, "已达到最大可管理容器数"),
|
||||||
|
Map.entry(0x0A000033, "密钥已被锁住"),
|
||||||
|
Map.entry(0x0A000034, "证书未被使用"),
|
||||||
|
Map.entry(0x0A000035, "无效容器"),
|
||||||
|
Map.entry(0x0A000036, "容器不存在"),
|
||||||
|
Map.entry(0x0A000037, "容器已存在"),
|
||||||
|
Map.entry(0x0A000038, "证书已经被使用"),
|
||||||
|
Map.entry(0x0A000039, "密钥未被使用"),
|
||||||
|
Map.entry(0x0A00003A, "文件操作权限错误"),
|
||||||
|
Map.entry(0x0A00003B, "设备未认证"),
|
||||||
|
Map.entry(0x0A00003C, "请重新插拔卡"),
|
||||||
|
Map.entry(0x0A00003D, "应用处于非活跃状态"),
|
||||||
|
Map.entry(0x0A00003E, "应用处于打开状态"),
|
||||||
|
Map.entry(0x0A000040, "传入的固件数据无效(格式错误)"),
|
||||||
|
Map.entry(0x0A000041, "传入的固件数据的签名值无效(验签失败)"),
|
||||||
|
Map.entry(0x0A0000A4, "MF不存在")
|
||||||
|
);
|
||||||
|
|
||||||
|
private SkfErrorCodeMessageResolver() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String resolve(int code) {
|
||||||
|
return CODE_MESSAGE_MAP.get(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.cisd.ukey.exception;
|
||||||
|
|
||||||
|
public class SkfException extends RuntimeException {
|
||||||
|
|
||||||
|
private final String apiName;
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
public SkfException(String apiName, int code, String message) {
|
||||||
|
super(message);
|
||||||
|
this.apiName = apiName;
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SkfException(String apiName, String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
this.apiName = apiName;
|
||||||
|
this.code = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getApiName() {
|
||||||
|
return apiName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.cisd.ukey.jna;
|
||||||
|
|
||||||
|
import com.sun.jna.Structure;
|
||||||
|
|
||||||
|
@Structure.FieldOrder({"iv", "ivLen", "paddingType", "feedBitLen"})
|
||||||
|
public class BlockCipherParam extends Structure {
|
||||||
|
|
||||||
|
public byte[] iv = new byte[32];
|
||||||
|
public int ivLen;
|
||||||
|
public int paddingType;
|
||||||
|
public int feedBitLen;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.cisd.ukey.jna;
|
||||||
|
|
||||||
|
import com.sun.jna.Structure;
|
||||||
|
|
||||||
|
@Structure.FieldOrder({
|
||||||
|
"version", "manufacturer", "issuer", "label", "serialNumber",
|
||||||
|
"hwVersion", "firmwareVersion", "algSymCap", "algAsymCap",
|
||||||
|
"algHashCap", "devAuthAlgId", "totalSpace", "freeSpace",
|
||||||
|
"maxEccBufferSize", "maxBufferSize", "reserved"
|
||||||
|
})
|
||||||
|
public class DevInfo extends Structure {
|
||||||
|
|
||||||
|
public DevInfo() {
|
||||||
|
setAlignType(ALIGN_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Version version;
|
||||||
|
public byte[] manufacturer = new byte[64];
|
||||||
|
public byte[] issuer = new byte[64];
|
||||||
|
public byte[] label = new byte[32];
|
||||||
|
public byte[] serialNumber = new byte[32];
|
||||||
|
public Version hwVersion;
|
||||||
|
public Version firmwareVersion;
|
||||||
|
public int algSymCap;
|
||||||
|
public int algAsymCap;
|
||||||
|
public int algHashCap;
|
||||||
|
public int devAuthAlgId;
|
||||||
|
public int totalSpace;
|
||||||
|
public int freeSpace;
|
||||||
|
public int maxEccBufferSize;
|
||||||
|
public int maxBufferSize;
|
||||||
|
public byte[] reserved = new byte[64];
|
||||||
|
|
||||||
|
public long devAuthAlgIdUnsigned() {
|
||||||
|
return Integer.toUnsignedLong(devAuthAlgId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.cisd.ukey.jna;
|
||||||
|
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.Structure;
|
||||||
|
|
||||||
|
@Structure.FieldOrder({"bitLen", "xCoordinate", "yCoordinate"})
|
||||||
|
public class EccPublicKeyBlob extends Structure {
|
||||||
|
|
||||||
|
public int bitLen;
|
||||||
|
public byte[] xCoordinate = new byte[64];
|
||||||
|
public byte[] yCoordinate = new byte[64];
|
||||||
|
|
||||||
|
public EccPublicKeyBlob() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public EccPublicKeyBlob(Pointer pointer) {
|
||||||
|
super(pointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.cisd.ukey.jna;
|
||||||
|
|
||||||
|
import com.sun.jna.Structure;
|
||||||
|
|
||||||
|
@Structure.FieldOrder({"r", "s"})
|
||||||
|
public class EccSignatureBlob extends Structure {
|
||||||
|
|
||||||
|
public byte[] r = new byte[64];
|
||||||
|
public byte[] s = new byte[64];
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.cisd.ukey.jna;
|
||||||
|
|
||||||
|
import com.sun.jna.Structure;
|
||||||
|
|
||||||
|
@Structure.FieldOrder({"fileName", "fileSize", "readRights", "writeRights"})
|
||||||
|
public class FileAttribute extends Structure {
|
||||||
|
|
||||||
|
public byte[] fileName = new byte[32];
|
||||||
|
public int fileSize;
|
||||||
|
public int readRights;
|
||||||
|
public int writeRights;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.cisd.ukey.jna;
|
||||||
|
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.ptr.IntByReference;
|
||||||
|
import com.sun.jna.ptr.PointerByReference;
|
||||||
|
import com.sun.jna.win32.StdCallLibrary;
|
||||||
|
|
||||||
|
public interface SkfLibrary extends StdCallLibrary {
|
||||||
|
|
||||||
|
int SKF_EnumDev(int present, Pointer nameList, IntByReference size);
|
||||||
|
|
||||||
|
int SKF_ConnectDev(byte[] name, PointerByReference devHandle);
|
||||||
|
|
||||||
|
int SKF_DisConnectDev(Pointer devHandle);
|
||||||
|
|
||||||
|
int SKF_GetDevInfo(Pointer devHandle, DevInfo devInfo);
|
||||||
|
|
||||||
|
int SKF_GenRandom(Pointer devHandle, byte[] random, int randomLen);
|
||||||
|
|
||||||
|
int SKF_DevAuth(Pointer devHandle, byte[] authData, int len);
|
||||||
|
|
||||||
|
int SKF_OpenApplication(Pointer devHandle, byte[] appName, PointerByReference applicationHandle);
|
||||||
|
|
||||||
|
int SKF_CreateApplication(Pointer devHandle, byte[] appName, byte[] adminPin, int adminPinRetryCount,
|
||||||
|
byte[] userPin, int userPinRetryCount, int createFileRights,
|
||||||
|
PointerByReference applicationHandle);
|
||||||
|
|
||||||
|
int SKF_DeleteApplication(Pointer devHandle, byte[] appName);
|
||||||
|
|
||||||
|
int SKF_CloseApplication(Pointer applicationHandle);
|
||||||
|
|
||||||
|
int SKF_VerifyPIN(Pointer applicationHandle, int pinType, byte[] pin, IntByReference retryCount);
|
||||||
|
|
||||||
|
int SKF_ChangePIN(Pointer applicationHandle, int pinType, byte[] oldPin, byte[] newPin, IntByReference retryCount);
|
||||||
|
|
||||||
|
int SKF_UnblockPIN(Pointer applicationHandle, byte[] adminPin, byte[] newUserPin, IntByReference retryCount);
|
||||||
|
|
||||||
|
int SKF_GetPINInfo(Pointer applicationHandle, int pinType, IntByReference maxRetryCount,
|
||||||
|
IntByReference remainRetryCount, IntByReference defaultPin);
|
||||||
|
|
||||||
|
int SKF_CreateFile(Pointer applicationHandle, byte[] fileName, int fileSize, int readRights, int writeRights);
|
||||||
|
|
||||||
|
int SKF_DeleteFile(Pointer applicationHandle, byte[] fileName);
|
||||||
|
|
||||||
|
int SKF_EnumFiles(Pointer applicationHandle, Pointer fileList, IntByReference size);
|
||||||
|
|
||||||
|
int SKF_GetFileInfo(Pointer applicationHandle, byte[] fileName, FileAttribute fileAttribute);
|
||||||
|
|
||||||
|
int SKF_ReadFile(Pointer applicationHandle, byte[] fileName, int offset, int size, Pointer outData, IntByReference outLen);
|
||||||
|
|
||||||
|
int SKF_WriteFile(Pointer applicationHandle, byte[] fileName, int offset, byte[] data, int size);
|
||||||
|
|
||||||
|
int SKF_OpenContainer(Pointer applicationHandle, byte[] containerName, PointerByReference containerHandle);
|
||||||
|
|
||||||
|
int SKF_CreateContainer(Pointer applicationHandle, byte[] containerName, PointerByReference containerHandle);
|
||||||
|
|
||||||
|
int SKF_GenECCKeyPair(Pointer containerHandle, int algId, EccPublicKeyBlob publicKeyBlob);
|
||||||
|
|
||||||
|
int SKF_CloseContainer(Pointer containerHandle);
|
||||||
|
|
||||||
|
int SKF_ECCSignData(Pointer containerHandle, byte[] data, int dataLen, EccSignatureBlob signature);
|
||||||
|
|
||||||
|
int SKF_ECCVerify(Pointer devHandle, EccPublicKeyBlob publicKey, byte[] data, int dataLen, EccSignatureBlob signature);
|
||||||
|
|
||||||
|
int SKF_ExportPublicKey(Pointer containerHandle, int signFlag, Pointer blob, IntByReference blobLen);
|
||||||
|
|
||||||
|
int SKF_SetSymmKey(Pointer devHandle, byte[] key, int algId, PointerByReference keyHandle);
|
||||||
|
|
||||||
|
int SKF_EncryptInit(Pointer keyHandle, BlockCipherParam encryptParam);
|
||||||
|
|
||||||
|
int SKF_Encrypt(Pointer keyHandle, byte[] data, int dataLen, byte[] encryptedData, IntByReference encryptedLen);
|
||||||
|
|
||||||
|
int SKF_CloseHandle(Pointer handle);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.cisd.ukey.jna;
|
||||||
|
|
||||||
|
import com.sun.jna.Structure;
|
||||||
|
|
||||||
|
@Structure.FieldOrder({"major", "minor"})
|
||||||
|
public class Version extends Structure {
|
||||||
|
|
||||||
|
public Version() {
|
||||||
|
setAlignType(ALIGN_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte major;
|
||||||
|
public byte minor;
|
||||||
|
}
|
||||||
@@ -0,0 +1,428 @@
|
|||||||
|
package com.cisd.ukey.service;
|
||||||
|
|
||||||
|
import org.bouncycastle.asn1.ASN1Encodable;
|
||||||
|
import org.bouncycastle.asn1.ASN1Encoding;
|
||||||
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||||
|
import org.bouncycastle.asn1.ASN1Primitive;
|
||||||
|
import org.bouncycastle.asn1.DERNull;
|
||||||
|
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||||
|
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||||
|
import org.bouncycastle.asn1.x9.X962Parameters;
|
||||||
|
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||||
|
import org.bouncycastle.asn1.x9.X9ECPoint;
|
||||||
|
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
|
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||||
|
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECKeyParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||||
|
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
|
||||||
|
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
|
||||||
|
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
|
||||||
|
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
|
||||||
|
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||||
|
import org.bouncycastle.math.ec.ECCurve;
|
||||||
|
import org.bouncycastle.math.ec.ECPoint;
|
||||||
|
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
|
||||||
|
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
import org.bouncycastle.util.io.pem.PemObject;
|
||||||
|
import org.bouncycastle.util.io.pem.PemReader;
|
||||||
|
import org.bouncycastle.util.io.pem.PemWriter;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.spec.ECGenParameterSpec;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这个工具类的方法,也适用于其他基于BC库的ECC算法
|
||||||
|
*/
|
||||||
|
public class BCECUtils {
|
||||||
|
private static final String ALGO_NAME_EC = "EC";
|
||||||
|
private static final String PEM_STRING_PUBLIC = "PUBLIC KEY";
|
||||||
|
private static final String PEM_STRING_ECPRIVATEKEY = "EC PRIVATE KEY";
|
||||||
|
|
||||||
|
|
||||||
|
public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
|
||||||
|
public final static BigInteger SM2_ECC_P = CURVE.getQ();
|
||||||
|
public final static BigInteger SM2_ECC_A = CURVE.getA().toBigInteger();
|
||||||
|
public final static BigInteger SM2_ECC_B = CURVE.getB().toBigInteger();
|
||||||
|
public final static BigInteger SM2_ECC_N = CURVE.getOrder();
|
||||||
|
public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
|
||||||
|
public final static BigInteger SM2_ECC_GX = new BigInteger(
|
||||||
|
"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
|
||||||
|
public final static BigInteger SM2_ECC_GY = new BigInteger(
|
||||||
|
"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
|
||||||
|
public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
|
||||||
|
public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT,
|
||||||
|
SM2_ECC_N, SM2_ECC_H);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成ECC密钥对
|
||||||
|
*
|
||||||
|
* @return ECC密钥对
|
||||||
|
*/
|
||||||
|
public static AsymmetricCipherKeyPair generateKeyPairParameter(
|
||||||
|
ECDomainParameters domainParameters, SecureRandom random) {
|
||||||
|
ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters,
|
||||||
|
random);
|
||||||
|
ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
|
||||||
|
keyGen.init(keyGenerationParams);
|
||||||
|
return keyGen.generateKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random)
|
||||||
|
throws NoSuchProviderException, NoSuchAlgorithmException,
|
||||||
|
InvalidAlgorithmParameterException {
|
||||||
|
KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
|
||||||
|
ECParameterSpec parameterSpec = new ECParameterSpec(domainParameters.getCurve(), domainParameters.getG(),
|
||||||
|
domainParameters.getN(), domainParameters.getH());
|
||||||
|
kpg.initialize(parameterSpec, random);
|
||||||
|
return kpg.generateKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getCurveLength(ECKeyParameters ecKey) {
|
||||||
|
return getCurveLength(ecKey.getParameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getCurveLength(ECDomainParameters domainParams) {
|
||||||
|
return (domainParams.getCurve().getFieldSize() + 7) / 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] fixToCurveLengthBytes(int curveLength, byte[] src) {
|
||||||
|
if (src.length == curveLength) {
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] result = new byte[curveLength];
|
||||||
|
if (src.length > curveLength) {
|
||||||
|
System.arraycopy(src, src.length - result.length, result, 0, result.length);
|
||||||
|
} else {
|
||||||
|
System.arraycopy(src, 0, result, result.length - src.length, src.length);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static String getHexPubKey(BCECPublicKey publicKey) {
|
||||||
|
byte[] x = publicKey.getQ().getXCoord().getEncoded();
|
||||||
|
byte[] y = publicKey.getQ().getYCoord().getEncoded();
|
||||||
|
return Hex.toHexString(x) + Hex.toHexString(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据EC私钥构造EC公钥
|
||||||
|
*
|
||||||
|
* @param priKey ECC私钥参数对象
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static ECPublicKeyParameters buildECPublicKeyByPrivateKey(ECPrivateKeyParameters priKey) {
|
||||||
|
ECDomainParameters domainParameters = priKey.getParameters();
|
||||||
|
ECPoint q = new FixedPointCombMultiplier().multiply(domainParameters.getG(), priKey.getD());
|
||||||
|
return new ECPublicKeyParameters(q, domainParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static ECPublicKeyParameters createECPublicKeyParameters(byte[] xBytes, byte[] yBytes) {
|
||||||
|
return createECPublicKeyParameters(xBytes, yBytes, CURVE, DOMAIN_PARAMS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param xBytes 十六进制形式的公钥x分量,如果是SM2算法,应该是32字节
|
||||||
|
* @param yBytes 十六进制形式的公钥y分量,如果是SM2算法,应该是32字节
|
||||||
|
* @param curve EC曲线参数,一般是固定的,如果是SM2算法的可参考{@link CURVE}
|
||||||
|
* @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link DOMAIN_PARAMS}
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static ECPublicKeyParameters createECPublicKeyParameters(
|
||||||
|
byte[] xBytes, byte[] yBytes, ECCurve curve, ECDomainParameters domainParameters) {
|
||||||
|
final byte uncompressedFlag = 0x04;
|
||||||
|
int curveLength = getCurveLength(domainParameters);
|
||||||
|
xBytes = fixToCurveLengthBytes(curveLength, xBytes);
|
||||||
|
yBytes = fixToCurveLengthBytes(curveLength, yBytes);
|
||||||
|
byte[] encodedPubKey = new byte[1 + xBytes.length + yBytes.length];
|
||||||
|
encodedPubKey[0] = uncompressedFlag;
|
||||||
|
System.arraycopy(xBytes, 0, encodedPubKey, 1, xBytes.length);
|
||||||
|
System.arraycopy(yBytes, 0, encodedPubKey, 1 + xBytes.length, yBytes.length);
|
||||||
|
return new ECPublicKeyParameters(curve.decodePoint(encodedPubKey), domainParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ECPrivateKeyParameters convertPrivateKeyToParameters(BCECPrivateKey ecPriKey) {
|
||||||
|
ECParameterSpec parameterSpec = ecPriKey.getParameters();
|
||||||
|
ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
|
||||||
|
parameterSpec.getN(), parameterSpec.getH());
|
||||||
|
return new ECPrivateKeyParameters(ecPriKey.getD(), domainParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ECPublicKeyParameters convertPublicKeyToParameters(BCECPublicKey ecPubKey) {
|
||||||
|
ECParameterSpec parameterSpec = ecPubKey.getParameters();
|
||||||
|
ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
|
||||||
|
parameterSpec.getN(), parameterSpec.getH());
|
||||||
|
return new ECPublicKeyParameters(ecPubKey.getQ(), domainParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BCECPublicKey createPublicKeyFromSubjectPublicKeyInfo(SubjectPublicKeyInfo subPubInfo)
|
||||||
|
throws NoSuchProviderException,
|
||||||
|
NoSuchAlgorithmException, InvalidKeySpecException, IOException {
|
||||||
|
return BCECUtils.convertX509ToECPublicKey(subPubInfo.toASN1Primitive().getEncoded(ASN1Encoding.DER));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将ECC私钥转换为PKCS8标准的字节流
|
||||||
|
*
|
||||||
|
* @param priKey
|
||||||
|
* @param pubKey 可以为空,但是如果为空的话得到的结果OpenSSL可能解析不了
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static byte[] convertECPrivateKeyToPKCS8(
|
||||||
|
ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) {
|
||||||
|
ECDomainParameters domainParams = priKey.getParameters();
|
||||||
|
ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(),
|
||||||
|
domainParams.getN(), domainParams.getH());
|
||||||
|
BCECPublicKey publicKey = null;
|
||||||
|
if (pubKey != null) {
|
||||||
|
publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec,
|
||||||
|
BouncyCastleProvider.CONFIGURATION);
|
||||||
|
}
|
||||||
|
BCECPrivateKey privateKey = new BCECPrivateKey(ALGO_NAME_EC, priKey, publicKey,
|
||||||
|
spec, BouncyCastleProvider.CONFIGURATION);
|
||||||
|
return privateKey.getEncoded();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将PKCS8标准的私钥字节流转换为私钥对象
|
||||||
|
*
|
||||||
|
* @param pkcs8Key
|
||||||
|
* @return
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
* @throws NoSuchProviderException
|
||||||
|
* @throws InvalidKeySpecException
|
||||||
|
*/
|
||||||
|
public static BCECPrivateKey convertPKCS8ToECPrivateKey(byte[] pkcs8Key)
|
||||||
|
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
|
||||||
|
PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(pkcs8Key);
|
||||||
|
KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
|
||||||
|
return (BCECPrivateKey) kf.generatePrivate(peks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将PKCS8标准的私钥字节流转换为PEM
|
||||||
|
*
|
||||||
|
* @param encodedKey
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static String convertECPrivateKeyPKCS8ToPEM(byte[] encodedKey) throws IOException {
|
||||||
|
return convertEncodedDataToPEM(PEM_STRING_ECPRIVATEKEY, encodedKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将PEM格式的私钥转换为PKCS8标准字节流
|
||||||
|
*
|
||||||
|
* @param pemString
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static byte[] convertECPrivateKeyPEMToPKCS8(String pemString) throws IOException {
|
||||||
|
return convertPEMToEncodedData(pemString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将ECC私钥转换为SEC1标准的字节流
|
||||||
|
* openssl d2i_ECPrivateKey函数要求的DER编码的私钥也是SEC1标准的,
|
||||||
|
* 这个工具函数的主要目的就是为了能生成一个openssl可以直接“识别”的ECC私钥.
|
||||||
|
* 相对RSA私钥的PKCS1标准,ECC私钥的标准为SEC1
|
||||||
|
*
|
||||||
|
* @param priKey
|
||||||
|
* @param pubKey
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static byte[] convertECPrivateKeyToSEC1(
|
||||||
|
ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) throws IOException {
|
||||||
|
byte[] pkcs8Bytes = convertECPrivateKeyToPKCS8(priKey, pubKey);
|
||||||
|
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(pkcs8Bytes);
|
||||||
|
ASN1Encodable encodable = pki.parsePrivateKey();
|
||||||
|
ASN1Primitive primitive = encodable.toASN1Primitive();
|
||||||
|
byte[] sec1Bytes = primitive.getEncoded();
|
||||||
|
return sec1Bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将ECC公钥对象转换为X509标准的字节流
|
||||||
|
*
|
||||||
|
* @param pubKey
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static byte[] convertECPublicKeyToX509(ECPublicKeyParameters pubKey) {
|
||||||
|
ECDomainParameters domainParams = pubKey.getParameters();
|
||||||
|
ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(),
|
||||||
|
domainParams.getN(), domainParams.getH());
|
||||||
|
BCECPublicKey publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec,
|
||||||
|
BouncyCastleProvider.CONFIGURATION);
|
||||||
|
return publicKey.getEncoded();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将X509标准的公钥字节流转为公钥对象
|
||||||
|
*
|
||||||
|
* @param x509Bytes
|
||||||
|
* @return
|
||||||
|
* @throws NoSuchProviderException
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
* @throws InvalidKeySpecException
|
||||||
|
*/
|
||||||
|
public static BCECPublicKey convertX509ToECPublicKey(byte[] x509Bytes) throws NoSuchProviderException,
|
||||||
|
NoSuchAlgorithmException, InvalidKeySpecException {
|
||||||
|
X509EncodedKeySpec eks = new X509EncodedKeySpec(x509Bytes);
|
||||||
|
KeyFactory kf = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
|
||||||
|
return (BCECPublicKey) kf.generatePublic(eks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将X509标准的公钥字节流转为PEM
|
||||||
|
*
|
||||||
|
* @param encodedKey
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static String convertECPublicKeyX509ToPEM(byte[] encodedKey) throws IOException {
|
||||||
|
return convertEncodedDataToPEM(PEM_STRING_PUBLIC, encodedKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将PEM格式的公钥转为X509标准的字节流
|
||||||
|
*
|
||||||
|
* @param pemString
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static byte[] convertECPublicKeyPEMToX509(String pemString) throws IOException {
|
||||||
|
return convertPEMToEncodedData(pemString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* copy from BC
|
||||||
|
*
|
||||||
|
* @param genSpec
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static X9ECParameters getDomainParametersFromGenSpec(ECGenParameterSpec genSpec) {
|
||||||
|
return getDomainParametersFromName(genSpec.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* copy from BC
|
||||||
|
*
|
||||||
|
* @param curveName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static X9ECParameters getDomainParametersFromName(String curveName) {
|
||||||
|
X9ECParameters domainParameters;
|
||||||
|
try {
|
||||||
|
if (curveName.charAt(0) >= '0' && curveName.charAt(0) <= '2') {
|
||||||
|
ASN1ObjectIdentifier oidID = new ASN1ObjectIdentifier(curveName);
|
||||||
|
domainParameters = ECUtil.getNamedCurveByOid(oidID);
|
||||||
|
} else {
|
||||||
|
if (curveName.indexOf(' ') > 0) {
|
||||||
|
curveName = curveName.substring(curveName.indexOf(' ') + 1);
|
||||||
|
domainParameters = ECUtil.getNamedCurveByName(curveName);
|
||||||
|
} else {
|
||||||
|
domainParameters = ECUtil.getNamedCurveByName(curveName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
domainParameters = ECUtil.getNamedCurveByName(curveName);
|
||||||
|
}
|
||||||
|
return domainParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* copy from BC
|
||||||
|
*
|
||||||
|
* @param ecSpec
|
||||||
|
* @param withCompression
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static X962Parameters getDomainParametersFromName(
|
||||||
|
java.security.spec.ECParameterSpec ecSpec, boolean withCompression) {
|
||||||
|
X962Parameters params;
|
||||||
|
|
||||||
|
if (ecSpec instanceof ECNamedCurveSpec) {
|
||||||
|
ASN1ObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec) ecSpec).getName());
|
||||||
|
if (curveOid == null) {
|
||||||
|
curveOid = new ASN1ObjectIdentifier(((ECNamedCurveSpec) ecSpec).getName());
|
||||||
|
}
|
||||||
|
params = new X962Parameters(curveOid);
|
||||||
|
} else if (ecSpec == null) {
|
||||||
|
params = new X962Parameters(DERNull.INSTANCE);
|
||||||
|
} else {
|
||||||
|
ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve());
|
||||||
|
|
||||||
|
X9ECParameters ecP = new X9ECParameters(
|
||||||
|
curve,
|
||||||
|
new X9ECPoint(EC5Util.convertPoint(curve, ecSpec.getGenerator()), withCompression),
|
||||||
|
ecSpec.getOrder(),
|
||||||
|
BigInteger.valueOf(ecSpec.getCofactor()),
|
||||||
|
ecSpec.getCurve().getSeed());
|
||||||
|
|
||||||
|
//// 如果是1.62或更低版本的bcprov-jdk15on应该使用以下这段代码,因为高版本的EC5Util.convertPoint没有向下兼容
|
||||||
|
/*
|
||||||
|
X9ECParameters ecP = new X9ECParameters(
|
||||||
|
curve,
|
||||||
|
EC5Util.convertPoint(curve, ecSpec.getGenerator(), withCompression),
|
||||||
|
ecSpec.getOrder(),
|
||||||
|
BigInteger.valueOf(ecSpec.getCofactor()),
|
||||||
|
ecSpec.getCurve().getSeed());
|
||||||
|
*/
|
||||||
|
|
||||||
|
params = new X962Parameters(ecP);
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String convertEncodedDataToPEM(String type, byte[] encodedData) throws IOException {
|
||||||
|
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
|
||||||
|
PemWriter pWrt = new PemWriter(new OutputStreamWriter(bOut));
|
||||||
|
try {
|
||||||
|
PemObject pemObj = new PemObject(type, encodedData);
|
||||||
|
pWrt.writeObject(pemObj);
|
||||||
|
} finally {
|
||||||
|
pWrt.close();
|
||||||
|
}
|
||||||
|
return new String(bOut.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] convertPEMToEncodedData(String pemString) throws IOException {
|
||||||
|
ByteArrayInputStream bIn = new ByteArrayInputStream(pemString.getBytes());
|
||||||
|
PemReader pRdr = new PemReader(new InputStreamReader(bIn));
|
||||||
|
try {
|
||||||
|
PemObject pemObject = pRdr.readPemObject();
|
||||||
|
return pemObject.getContent();
|
||||||
|
} finally {
|
||||||
|
pRdr.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
package com.cisd.ukey.service;
|
||||||
|
|
||||||
|
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||||
|
import org.bouncycastle.asn1.ASN1Encoding;
|
||||||
|
import org.bouncycastle.asn1.ASN1Integer;
|
||||||
|
import org.bouncycastle.asn1.ASN1Primitive;
|
||||||
|
import org.bouncycastle.asn1.ASN1Sequence;
|
||||||
|
import org.bouncycastle.asn1.DERSequence;
|
||||||
|
import org.bouncycastle.crypto.CipherParameters;
|
||||||
|
import org.bouncycastle.crypto.CryptoException;
|
||||||
|
import org.bouncycastle.crypto.CryptoServicesRegistrar;
|
||||||
|
import org.bouncycastle.crypto.Digest;
|
||||||
|
import org.bouncycastle.crypto.digests.SM3Digest;
|
||||||
|
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECKeyParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ParametersWithID;
|
||||||
|
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
||||||
|
import org.bouncycastle.crypto.signers.DSAKCalculator;
|
||||||
|
import org.bouncycastle.crypto.signers.RandomDSAKCalculator;
|
||||||
|
import org.bouncycastle.math.ec.ECAlgorithms;
|
||||||
|
import org.bouncycastle.math.ec.ECConstants;
|
||||||
|
import org.bouncycastle.math.ec.ECFieldElement;
|
||||||
|
import org.bouncycastle.math.ec.ECMultiplier;
|
||||||
|
import org.bouncycastle.math.ec.ECPoint;
|
||||||
|
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
|
||||||
|
import org.bouncycastle.util.Arrays;
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有的国密需求是用户可以自己做预处理,签名验签只是对预处理的结果进行签名和验签
|
||||||
|
*/
|
||||||
|
public class SM2PreprocessSigner implements ECConstants {
|
||||||
|
private static final int DIGEST_LENGTH = 32; // bytes
|
||||||
|
|
||||||
|
private final DSAKCalculator kCalculator = new RandomDSAKCalculator();
|
||||||
|
private Digest digest = null;
|
||||||
|
|
||||||
|
private ECDomainParameters ecParams;
|
||||||
|
private ECPoint pubPoint;
|
||||||
|
private ECKeyParameters ecKey;
|
||||||
|
private byte[] userID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*
|
||||||
|
* @param forSigning true表示用于签名,false表示用于验签
|
||||||
|
* @param param
|
||||||
|
*/
|
||||||
|
public void init(boolean forSigning, CipherParameters param) {
|
||||||
|
init(forSigning, new SM3Digest(), param);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*
|
||||||
|
* @param forSigning true表示用于签名,false表示用于验签
|
||||||
|
* @param digest SM2算法的话,一般是采用SM3摘要算法
|
||||||
|
* @param param
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
public void init(boolean forSigning, Digest digest, CipherParameters param) throws RuntimeException {
|
||||||
|
CipherParameters baseParam;
|
||||||
|
|
||||||
|
if (digest.getDigestSize() != DIGEST_LENGTH) {
|
||||||
|
throw new RuntimeException("Digest size must be " + DIGEST_LENGTH);
|
||||||
|
}
|
||||||
|
this.digest = digest;
|
||||||
|
|
||||||
|
if (param instanceof ParametersWithID) {
|
||||||
|
baseParam = ((ParametersWithID) param).getParameters();
|
||||||
|
userID = ((ParametersWithID) param).getID();
|
||||||
|
} else {
|
||||||
|
baseParam = param;
|
||||||
|
userID = Hex.decode("31323334353637383132333435363738"); // the default value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forSigning) {
|
||||||
|
if (baseParam instanceof ParametersWithRandom) {
|
||||||
|
ParametersWithRandom rParam = (ParametersWithRandom) baseParam;
|
||||||
|
|
||||||
|
ecKey = (ECKeyParameters) rParam.getParameters();
|
||||||
|
ecParams = ecKey.getParameters();
|
||||||
|
kCalculator.init(ecParams.getN(), rParam.getRandom());
|
||||||
|
} else {
|
||||||
|
ecKey = (ECKeyParameters) baseParam;
|
||||||
|
ecParams = ecKey.getParameters();
|
||||||
|
kCalculator.init(ecParams.getN(), CryptoServicesRegistrar.getSecureRandom());
|
||||||
|
}
|
||||||
|
pubPoint = createBasePointMultiplier().multiply(ecParams.getG(), ((ECPrivateKeyParameters) ecKey).getD()).normalize();
|
||||||
|
} else {
|
||||||
|
ecKey = (ECKeyParameters) baseParam;
|
||||||
|
ecParams = ecKey.getParameters();
|
||||||
|
pubPoint = ((ECPublicKeyParameters) ecKey).getQ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预处理,辅助方法
|
||||||
|
* ZA=H256(ENT LA ∥ IDA ∥ a ∥ b ∥ xG ∥yG ∥ xA ∥ yA)。
|
||||||
|
* M=ZA ∥ M;
|
||||||
|
* e = Hv(M)
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public byte[] preprocess(byte[] m) {
|
||||||
|
return preprocess(m, 0, m.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public byte[] preprocess(byte[] m, int off, int len) {
|
||||||
|
byte[] z = getZ(userID);
|
||||||
|
digest.update(z, 0, z.length);
|
||||||
|
digest.update(m, off, len);
|
||||||
|
byte[] eHash = new byte[DIGEST_LENGTH];
|
||||||
|
digest.doFinal(eHash, 0);
|
||||||
|
return eHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean verifySignature(byte[] eHash, byte[] signature) {
|
||||||
|
try {
|
||||||
|
BigInteger[] rs = derDecode(signature);
|
||||||
|
if (rs != null) {
|
||||||
|
return verifySignature(eHash, rs[0], rs[1]);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
digest.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] generateSignature(byte[] eHash) throws CryptoException {
|
||||||
|
BigInteger n = ecParams.getN();
|
||||||
|
BigInteger e = calculateE(eHash);
|
||||||
|
BigInteger d = ((ECPrivateKeyParameters) ecKey).getD();
|
||||||
|
|
||||||
|
BigInteger r, s;
|
||||||
|
|
||||||
|
ECMultiplier basePointMultiplier = createBasePointMultiplier();
|
||||||
|
|
||||||
|
// 5.2.1 Draft RFC: SM2 Public Key Algorithms
|
||||||
|
do // generate s
|
||||||
|
{
|
||||||
|
BigInteger k;
|
||||||
|
do // generate r
|
||||||
|
{
|
||||||
|
// A3
|
||||||
|
k = kCalculator.nextK();
|
||||||
|
|
||||||
|
// A4
|
||||||
|
ECPoint p = basePointMultiplier.multiply(ecParams.getG(), k).normalize();
|
||||||
|
|
||||||
|
// A5
|
||||||
|
r = e.add(p.getAffineXCoord().toBigInteger()).mod(n);
|
||||||
|
}
|
||||||
|
while (r.equals(ZERO) || r.add(k).equals(n));
|
||||||
|
|
||||||
|
// A6
|
||||||
|
BigInteger dPlus1ModN = d.add(ONE).modInverse(n);
|
||||||
|
|
||||||
|
s = k.subtract(r.multiply(d)).mod(n);
|
||||||
|
s = dPlus1ModN.multiply(s).mod(n);
|
||||||
|
}
|
||||||
|
while (s.equals(ZERO));
|
||||||
|
|
||||||
|
// A7
|
||||||
|
try {
|
||||||
|
return derEncode(r, s);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new CryptoException("unable to encode signature: " + ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean verifySignature(byte[] eHash, BigInteger r, BigInteger s) {
|
||||||
|
BigInteger n = ecParams.getN();
|
||||||
|
|
||||||
|
// 5.3.1 Draft RFC: SM2 Public Key Algorithms
|
||||||
|
// B1
|
||||||
|
if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// B2
|
||||||
|
if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// B3 eHash
|
||||||
|
|
||||||
|
// B4
|
||||||
|
BigInteger e = calculateE(eHash);
|
||||||
|
|
||||||
|
// B5
|
||||||
|
BigInteger t = r.add(s).mod(n);
|
||||||
|
if (t.equals(ZERO)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// B6
|
||||||
|
ECPoint q = ((ECPublicKeyParameters) ecKey).getQ();
|
||||||
|
ECPoint x1y1 = ECAlgorithms.sumOfTwoMultiplies(ecParams.getG(), s, q, t).normalize();
|
||||||
|
if (x1y1.isInfinity()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// B7
|
||||||
|
BigInteger expectedR = e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n);
|
||||||
|
|
||||||
|
return expectedR.equals(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] digestDoFinal() {
|
||||||
|
byte[] result = new byte[digest.getDigestSize()];
|
||||||
|
digest.doFinal(result, 0);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getZ(byte[] userID) {
|
||||||
|
digest.reset();
|
||||||
|
|
||||||
|
addUserID(digest, userID);
|
||||||
|
|
||||||
|
addFieldElement(digest, ecParams.getCurve().getA());
|
||||||
|
addFieldElement(digest, ecParams.getCurve().getB());
|
||||||
|
addFieldElement(digest, ecParams.getG().getAffineXCoord());
|
||||||
|
addFieldElement(digest, ecParams.getG().getAffineYCoord());
|
||||||
|
addFieldElement(digest, pubPoint.getAffineXCoord());
|
||||||
|
addFieldElement(digest, pubPoint.getAffineYCoord());
|
||||||
|
|
||||||
|
byte[] result = new byte[digest.getDigestSize()];
|
||||||
|
|
||||||
|
digest.doFinal(result, 0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addUserID(Digest digest, byte[] userID) {
|
||||||
|
int len = userID.length * 8;
|
||||||
|
digest.update((byte) (len >> 8 & 0xFF));
|
||||||
|
digest.update((byte) (len & 0xFF));
|
||||||
|
digest.update(userID, 0, userID.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFieldElement(Digest digest, ECFieldElement v) {
|
||||||
|
byte[] p = v.getEncoded();
|
||||||
|
digest.update(p, 0, p.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ECMultiplier createBasePointMultiplier() {
|
||||||
|
return new FixedPointCombMultiplier();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BigInteger calculateE(byte[] message) {
|
||||||
|
return new BigInteger(1, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BigInteger[] derDecode(byte[] encoding)
|
||||||
|
throws IOException {
|
||||||
|
ASN1Sequence seq = ASN1Sequence.getInstance(ASN1Primitive.fromByteArray(encoding));
|
||||||
|
if (seq.size() != 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
BigInteger r = ASN1Integer.getInstance(seq.getObjectAt(0)).getValue();
|
||||||
|
BigInteger s = ASN1Integer.getInstance(seq.getObjectAt(1)).getValue();
|
||||||
|
|
||||||
|
byte[] expectedEncoding = derEncode(r, s);
|
||||||
|
if (!Arrays.constantTimeAreEqual(expectedEncoding, encoding)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BigInteger[]{r, s};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected byte[] derEncode(BigInteger r, BigInteger s)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
ASN1EncodableVector v = new ASN1EncodableVector();
|
||||||
|
v.add(new ASN1Integer(r));
|
||||||
|
v.add(new ASN1Integer(s));
|
||||||
|
return new DERSequence(v).getEncoded(ASN1Encoding.DER);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
package com.cisd.ukey.service;
|
||||||
|
|
||||||
|
import com.cisd.ukey.config.SkfProperties;
|
||||||
|
import com.cisd.ukey.jna.DevInfo;
|
||||||
|
import com.cisd.ukey.jna.EccPublicKeyBlob;
|
||||||
|
import com.cisd.ukey.jna.EccSignatureBlob;
|
||||||
|
import com.cisd.ukey.jna.FileAttribute;
|
||||||
|
import com.cisd.ukey.jna.SkfLibrary;
|
||||||
|
import com.sun.jna.Memory;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.ptr.IntByReference;
|
||||||
|
import org.bouncycastle.crypto.CipherParameters;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SkfService {
|
||||||
|
|
||||||
|
public static final int ADMIN_TYPE = 0;
|
||||||
|
public static final int USER_TYPE = 1;
|
||||||
|
private static final int SAR_APPLICATION_NOT_EXISTS = 0x0A00002E;
|
||||||
|
private static final int SAR_FILE_ALREADY_EXIST = 0x0A00002F;
|
||||||
|
private static final int SAR_FILE_NOT_EXIST = 0x0A000031;
|
||||||
|
private static final int FILE_ACCESS_ANYONE = 0x000000FF;
|
||||||
|
|
||||||
|
private final SkfSessionTemplate sessionTemplate;
|
||||||
|
private final SkfProperties properties;
|
||||||
|
|
||||||
|
public SkfService(SkfSessionTemplate sessionTemplate, SkfProperties properties) {
|
||||||
|
this.sessionTemplate = sessionTemplate;
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DeviceInfo> listDevices(boolean presentOnly) {
|
||||||
|
SkfLibrary lib = sessionTemplate.library();
|
||||||
|
IntByReference size = new IntByReference();
|
||||||
|
sessionTemplate.ensureSuccess("SKF_EnumDev", lib.SKF_EnumDev(presentOnly ? 1 : 0, Pointer.NULL, size));
|
||||||
|
Memory buffer = new Memory(size.getValue());
|
||||||
|
sessionTemplate.ensureSuccess("SKF_EnumDev", lib.SKF_EnumDev(presentOnly ? 1 : 0, buffer, size));
|
||||||
|
List<DeviceInfo> result = new ArrayList<>();
|
||||||
|
for (String deviceName : parseMultiString(buffer.getByteArray(0, size.getValue()))) {
|
||||||
|
result.add(sessionTemplate.withDevice("listDevices", deviceName, (nativeLib, devHandle) -> {
|
||||||
|
DevInfo devInfo = new DevInfo();
|
||||||
|
sessionTemplate.ensureSuccess("SKF_GetDevInfo", nativeLib.SKF_GetDevInfo(devHandle, devInfo));
|
||||||
|
devInfo.read();
|
||||||
|
return new DeviceInfo(
|
||||||
|
deviceName,
|
||||||
|
trimCString(devInfo.serialNumber),
|
||||||
|
trimCString(devInfo.label),
|
||||||
|
trimCString(devInfo.manufacturer),
|
||||||
|
trimCString(devInfo.issuer)
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PinVerifyResult verifyPin(String deviceName, int pinType, String pin) {
|
||||||
|
return sessionTemplate.withApplication("verifyPin", deviceName, properties.getApplicationName(), (lib, devHandle, appHandle) -> {
|
||||||
|
IntByReference retryCount = new IntByReference();
|
||||||
|
sessionTemplate.ensureSuccess("SKF_VerifyPIN",
|
||||||
|
lib.SKF_VerifyPIN(appHandle, pinType, sessionTemplate.cString(pin), retryCount));
|
||||||
|
return pinInfo(lib, appHandle, pinType, retryCount.getValue());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public PinVerifyResult changePin(String deviceName, int pinType, String oldPin, String newPin) {
|
||||||
|
return sessionTemplate.withApplication("changePin", deviceName, properties.getApplicationName(), (lib, devHandle, appHandle) -> {
|
||||||
|
IntByReference retryCount = new IntByReference();
|
||||||
|
sessionTemplate.ensureSuccess("SKF_ChangePIN",
|
||||||
|
lib.SKF_ChangePIN(appHandle, pinType, sessionTemplate.cString(oldPin), sessionTemplate.cString(newPin), retryCount));
|
||||||
|
return pinInfo(lib, appHandle, pinType, retryCount.getValue());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public PinVerifyResult unblockPin(String deviceName, String adminPin, String newUserPin) {
|
||||||
|
verifyPin(deviceName, ADMIN_TYPE, adminPin);
|
||||||
|
return sessionTemplate.withApplication("resetPin", deviceName, properties.getApplicationName(), (lib, devHandle, appHandle) -> {
|
||||||
|
IntByReference retryCount = new IntByReference();
|
||||||
|
sessionTemplate.ensureSuccess("SKF_UnblockPIN",
|
||||||
|
lib.SKF_UnblockPIN(appHandle, sessionTemplate.cString(adminPin), sessionTemplate.cString(newUserPin), retryCount));
|
||||||
|
return pinInfo(lib, appHandle, USER_TYPE, retryCount.getValue());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FileInfo> listFiles(String deviceName) {
|
||||||
|
return sessionTemplate.withApplication("listFiles", deviceName, properties.getApplicationName(), (lib, devHandle, appHandle) -> {
|
||||||
|
IntByReference size = new IntByReference();
|
||||||
|
sessionTemplate.ensureSuccess("SKF_EnumFiles", lib.SKF_EnumFiles(appHandle, Pointer.NULL, size));
|
||||||
|
Memory buffer = new Memory(size.getValue());
|
||||||
|
sessionTemplate.ensureSuccess("SKF_EnumFiles", lib.SKF_EnumFiles(appHandle, buffer, size));
|
||||||
|
List<FileInfo> result = new ArrayList<>();
|
||||||
|
for (String fileName : parseMultiString(buffer.getByteArray(0, size.getValue()))) {
|
||||||
|
FileAttribute attribute = new FileAttribute();
|
||||||
|
sessionTemplate.ensureSuccess("SKF_GetFileInfo",
|
||||||
|
lib.SKF_GetFileInfo(appHandle, sessionTemplate.cString(fileName), attribute));
|
||||||
|
attribute.read();
|
||||||
|
result.add(new FileInfo(fileName, attribute.fileSize, attribute.readRights, attribute.writeRights));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileContent readFile(String deviceName, String fileName) {
|
||||||
|
return sessionTemplate.withApplication("readFile", deviceName, properties.getApplicationName(), (lib, devHandle, appHandle) -> {
|
||||||
|
FileAttribute attribute = new FileAttribute();
|
||||||
|
sessionTemplate.ensureSuccess("SKF_GetFileInfo",
|
||||||
|
lib.SKF_GetFileInfo(appHandle, sessionTemplate.cString(fileName), attribute));
|
||||||
|
attribute.read();
|
||||||
|
|
||||||
|
int readSize = attribute.fileSize;
|
||||||
|
if (readSize <= 0) {
|
||||||
|
return new FileContent(fileName, 0, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
Memory buffer = new Memory(readSize);
|
||||||
|
IntByReference outLen = new IntByReference(readSize);
|
||||||
|
sessionTemplate.ensureSuccess("SKF_ReadFile",
|
||||||
|
lib.SKF_ReadFile(appHandle, sessionTemplate.cString(fileName), 0, readSize, buffer, outLen));
|
||||||
|
byte[] data = buffer.getByteArray(0, outLen.getValue());
|
||||||
|
return new FileContent(fileName, outLen.getValue(), new String(data, StandardCharsets.UTF_8));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileContent writeFile(String deviceName, String fileName, String content) {
|
||||||
|
byte[] data = content.getBytes(StandardCharsets.UTF_8);
|
||||||
|
return sessionTemplate.withApplication("writeFile", deviceName, properties.getApplicationName(), (lib, devHandle, appHandle) -> {
|
||||||
|
FileAttribute attribute = new FileAttribute();
|
||||||
|
int infoRc = lib.SKF_GetFileInfo(appHandle, sessionTemplate.cString(fileName), attribute);
|
||||||
|
if (infoRc == 0) {
|
||||||
|
throw new com.cisd.ukey.exception.SkfException("SKF_WriteFile", SAR_FILE_ALREADY_EXIST, "文件已被写入");
|
||||||
|
}
|
||||||
|
if (infoRc != SAR_FILE_NOT_EXIST) {
|
||||||
|
sessionTemplate.ensureSuccess("SKF_GetFileInfo", infoRc);
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionTemplate.ensureSuccess("SKF_CreateFile",
|
||||||
|
lib.SKF_CreateFile(appHandle, sessionTemplate.cString(fileName), data.length, FILE_ACCESS_ANYONE, FILE_ACCESS_ANYONE));
|
||||||
|
sessionTemplate.ensureSuccess("SKF_WriteFile",
|
||||||
|
lib.SKF_WriteFile(appHandle, sessionTemplate.cString(fileName), 0, data, data.length));
|
||||||
|
return new FileContent(fileName, data.length, content);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteFile(String deviceName, String fileName) {
|
||||||
|
sessionTemplate.withApplication("deleteFile", deviceName, properties.getApplicationName(), (lib, devHandle, appHandle) -> {
|
||||||
|
sessionTemplate.ensureSuccess("SKF_DeleteFile", lib.SKF_DeleteFile(appHandle, sessionTemplate.cString(fileName)));
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteApplication(String deviceName) {
|
||||||
|
sessionTemplate.withAuthenticatedDevice("deleteApplication", deviceName, (lib, devHandle) -> {
|
||||||
|
int rc = lib.SKF_DeleteApplication(devHandle, sessionTemplate.cString(properties.getApplicationName()));
|
||||||
|
if (rc != 0 && rc != SAR_APPLICATION_NOT_EXISTS) {
|
||||||
|
sessionTemplate.ensureSuccess("SKF_DeleteApplication", rc);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public EccPublicKeyResult exportEccPublicKey(String deviceName, boolean signFlag) {
|
||||||
|
return sessionTemplate.withContainer("exportEccPublicKey", deviceName, properties.getApplicationName(), properties.getContainerName(),
|
||||||
|
(lib, devHandle, appHandle, containerHandle) -> {
|
||||||
|
IntByReference blobLen = new IntByReference();
|
||||||
|
sessionTemplate.ensureSuccess("SKF_ExportPublicKey",
|
||||||
|
lib.SKF_ExportPublicKey(containerHandle, signFlag ? 1 : 0, Pointer.NULL, blobLen));
|
||||||
|
Memory blob = new Memory(blobLen.getValue());
|
||||||
|
sessionTemplate.ensureSuccess("SKF_ExportPublicKey",
|
||||||
|
lib.SKF_ExportPublicKey(containerHandle, signFlag ? 1 : 0, blob, blobLen));
|
||||||
|
byte[] raw = blob.getByteArray(0, blobLen.getValue());
|
||||||
|
return new EccPublicKeyResult(
|
||||||
|
signFlag,
|
||||||
|
Base64.getEncoder().encodeToString(raw)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public EccSignatureResult eccSign(String deviceName, String plainText) {
|
||||||
|
EccPublicKeyResult eccPublicKeyResult = exportEccPublicKey(deviceName, true);
|
||||||
|
byte[] data = preprocessForSm2Sign(eccPublicKeyResult.pubKey, plainText);
|
||||||
|
return sessionTemplate.withContainer("eccSign", deviceName, properties.getApplicationName(), properties.getContainerName(),
|
||||||
|
(lib, devHandle, appHandle, containerHandle) -> {
|
||||||
|
EccSignatureBlob signature = new EccSignatureBlob();
|
||||||
|
sessionTemplate.ensureSuccess("SKF_ECCSignData",
|
||||||
|
lib.SKF_ECCSignData(containerHandle, data, data.length, signature));
|
||||||
|
signature.read();
|
||||||
|
return new EccSignatureResult(
|
||||||
|
Base64.getEncoder().encodeToString(signature.getPointer().getByteArray(0, signature.size())),
|
||||||
|
toHex(signature.r),
|
||||||
|
toHex(signature.s)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public VerifyResult eccVerify(String deviceName, String pubKey, String plainText, String signatureBase64) {
|
||||||
|
byte[] data = preprocessForSm2Sign(pubKey, plainText);
|
||||||
|
byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64);
|
||||||
|
byte[] pubkeyBytes = Base64.getDecoder().decode(pubKey);
|
||||||
|
return sessionTemplate.withContainer("eccVerify", deviceName, properties.getApplicationName(), properties.getContainerName(),
|
||||||
|
(lib, devHandle, appHandle, containerHandle) -> {
|
||||||
|
EccPublicKeyBlob publicKey = new EccPublicKeyBlob();
|
||||||
|
publicKey.getPointer().write(0, pubkeyBytes, 0, Math.min(pubkeyBytes.length, publicKey.size()));
|
||||||
|
publicKey.read();
|
||||||
|
|
||||||
|
EccSignatureBlob signature = new EccSignatureBlob();
|
||||||
|
signature.write();
|
||||||
|
signature.getPointer().write(0, signatureBytes, 0, Math.min(signatureBytes.length, signature.size()));
|
||||||
|
signature.read();
|
||||||
|
|
||||||
|
sessionTemplate.ensureSuccess("SKF_ECCVerify",
|
||||||
|
lib.SKF_ECCVerify(devHandle, publicKey, data, data.length, signature));
|
||||||
|
return new VerifyResult(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] preprocessForSm2Sign(String publicKey, String plainText) {
|
||||||
|
byte[] source = plainText.getBytes(StandardCharsets.UTF_8);
|
||||||
|
byte[] pubBytes = Base64.getDecoder().decode(publicKey);
|
||||||
|
byte[] x = Arrays.copyOfRange(pubBytes, 36, 68);
|
||||||
|
byte[] y = Arrays.copyOfRange(pubBytes, 100, 132);
|
||||||
|
|
||||||
|
CipherParameters parameters = BCECUtils.createECPublicKeyParameters(x, y);
|
||||||
|
SM2PreprocessSigner signer = new SM2PreprocessSigner();
|
||||||
|
signer.init(false, parameters);
|
||||||
|
byte[] preHash = signer.preprocess(source);
|
||||||
|
return preHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PinVerifyResult pinInfo(SkfLibrary lib, Pointer applicationHandle, int pinType, int retryCountFromCall) {
|
||||||
|
IntByReference maxRetryCount = new IntByReference();
|
||||||
|
IntByReference remainRetryCount = new IntByReference();
|
||||||
|
IntByReference defaultPin = new IntByReference();
|
||||||
|
sessionTemplate.ensureSuccess("SKF_GetPINInfo",
|
||||||
|
lib.SKF_GetPINInfo(applicationHandle, pinType, maxRetryCount, remainRetryCount, defaultPin));
|
||||||
|
return new PinVerifyResult(pinType, maxRetryCount.getValue(), remainRetryCount.getValue(), defaultPin.getValue() != 0, retryCountFromCall);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> parseMultiString(byte[] bytes) {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
int start = 0;
|
||||||
|
for (int i = 0; i < bytes.length; i++) {
|
||||||
|
if (bytes[i] != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (i == start) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result.add(new String(bytes, start, i - start, StandardCharsets.UTF_8));
|
||||||
|
start = i + 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trimCString(byte[] bytes) {
|
||||||
|
int len = 0;
|
||||||
|
while (len < bytes.length && bytes[len] != 0) {
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
return new String(bytes, 0, len, StandardCharsets.UTF_8).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toHex(byte[] bytes) {
|
||||||
|
StringBuilder builder = new StringBuilder(bytes.length * 2);
|
||||||
|
for (byte value : bytes) {
|
||||||
|
builder.append(String.format("%02X", value));
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] fromHex(String hex, int maxSize) {
|
||||||
|
byte[] result = new byte[maxSize];
|
||||||
|
int usable = Math.min(hex.length() / 2, maxSize);
|
||||||
|
for (int i = 0; i < usable; i++) {
|
||||||
|
int index = i * 2;
|
||||||
|
result[i] = (byte) Integer.parseInt(hex.substring(index, index + 2), 16);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record DeviceInfo(String deviceName, String serialNumber, String label, String manufacturer, String issuer) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record PinVerifyResult(int pinType, int maxRetryCount, int remainRetryCount, boolean defaultPin, int retryCountFromCall) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record FileInfo(String fileName, int fileSize, int readRights, int writeRights) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record FileContent(String fileName, int size, String content) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record EccPublicKeyResult(boolean signFlag, String pubKey) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record EccSignatureResult(String signatureBase64, String rHex, String sHex) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record VerifyResult(boolean success) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
package com.cisd.ukey.service;
|
||||||
|
|
||||||
|
import com.cisd.ukey.config.SkfProperties;
|
||||||
|
import com.cisd.ukey.exception.SkfException;
|
||||||
|
import com.cisd.ukey.jna.BlockCipherParam;
|
||||||
|
import com.cisd.ukey.jna.DevInfo;
|
||||||
|
import com.cisd.ukey.jna.EccPublicKeyBlob;
|
||||||
|
import com.cisd.ukey.jna.SkfLibrary;
|
||||||
|
import com.sun.jna.Library;
|
||||||
|
import com.sun.jna.Native;
|
||||||
|
import com.sun.jna.NativeLibrary;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.ptr.IntByReference;
|
||||||
|
import com.sun.jna.ptr.PointerByReference;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SkfSessionTemplate {
|
||||||
|
|
||||||
|
private static final int SAR_APPLICATION_NOT_EXISTS = 0x0A00002E;
|
||||||
|
private static final int SAR_CONTAINER_NOT_EXISTS = 0x0A000036;
|
||||||
|
private static final int SGD_SM1 = 0x00000100;
|
||||||
|
private static final int SGD_SM4 = 0x00000400;
|
||||||
|
private static final int SGD_AES128 = 0x00000800;
|
||||||
|
private static final int SGD_AES192 = 0x00000810;
|
||||||
|
private static final int SGD_AES256 = 0x00000820;
|
||||||
|
private static final int SGD_DES = 0x00001000;
|
||||||
|
private static final int SGD_D3DES = 0x00001010;
|
||||||
|
private static final int SGD_T3DES = 0x00001020;
|
||||||
|
private static final int SGD_SM7 = 0x00002000;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface DeviceCallback<T> {
|
||||||
|
T apply(SkfLibrary lib, Pointer devHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ApplicationCallback<T> {
|
||||||
|
T apply(SkfLibrary lib, Pointer devHandle, Pointer applicationHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ContainerCallback<T> {
|
||||||
|
T apply(SkfLibrary lib, Pointer devHandle, Pointer applicationHandle, Pointer containerHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Object lock = new Object();
|
||||||
|
private final SkfProperties properties;
|
||||||
|
private volatile SkfLibrary library;
|
||||||
|
|
||||||
|
public SkfSessionTemplate(SkfProperties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T withDevice(String apiName, String deviceName, DeviceCallback<T> callback) {
|
||||||
|
synchronized (lock) {
|
||||||
|
SkfLibrary lib = ensureLibraryLoaded();
|
||||||
|
Pointer devHandle = connectDevice(apiName, lib, deviceName);
|
||||||
|
try {
|
||||||
|
return callback.apply(lib, devHandle);
|
||||||
|
} finally {
|
||||||
|
disconnectQuietly(lib, devHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T withApplication(String apiName, String deviceName, String applicationName, ApplicationCallback<T> callback) {
|
||||||
|
return withDevice(apiName, deviceName, (lib, devHandle) -> {
|
||||||
|
Pointer applicationHandle = ensureApplication(apiName, lib, devHandle, applicationName);
|
||||||
|
try {
|
||||||
|
return callback.apply(lib, devHandle, applicationHandle);
|
||||||
|
} finally {
|
||||||
|
closeApplicationQuietly(lib, applicationHandle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T withAuthenticatedDevice(String apiName, String deviceName, DeviceCallback<T> callback) {
|
||||||
|
return withDevice(apiName, deviceName, (lib, devHandle) -> {
|
||||||
|
authenticateDevice(apiName, lib, devHandle);
|
||||||
|
return callback.apply(lib, devHandle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T withContainer(String apiName, String deviceName, String applicationName, String containerName,
|
||||||
|
ContainerCallback<T> callback) {
|
||||||
|
return withApplication(apiName, deviceName, applicationName, (lib, devHandle, applicationHandle) -> {
|
||||||
|
Pointer containerHandle = ensureContainer(apiName, lib, applicationHandle, containerName);
|
||||||
|
try {
|
||||||
|
return callback.apply(lib, devHandle, applicationHandle, containerHandle);
|
||||||
|
} finally {
|
||||||
|
closeContainerQuietly(lib, containerHandle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public SkfLibrary library() {
|
||||||
|
return ensureLibraryLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ensureSuccess(String apiName, int retCode) {
|
||||||
|
if (retCode != 0) {
|
||||||
|
throw new SkfException(apiName, retCode, apiName + " failed, ret=0x" + Integer.toHexString(retCode).toUpperCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] cString(String value) {
|
||||||
|
return (value + "\0").getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SkfLibrary ensureLibraryLoaded() {
|
||||||
|
SkfLibrary cached = library;
|
||||||
|
if (cached != null) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
if (library != null) {
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
Map<String, Object> options = new HashMap<>();
|
||||||
|
options.put(Library.OPTION_STRING_ENCODING, "UTF-8");
|
||||||
|
try {
|
||||||
|
Path extractDir = Path.of(properties.getLibDir()).toAbsolutePath().normalize();
|
||||||
|
Path libPath = extractDir.resolve(properties.getVendorLibName()).toAbsolutePath().normalize();
|
||||||
|
NativeLibrary.addSearchPath(stripExtension(properties.getVendorLibName()), extractDir.toString());
|
||||||
|
library = Native.load(libPath.toString(), SkfLibrary.class, options);
|
||||||
|
} catch (UnsatisfiedLinkError ex) {
|
||||||
|
throw new SkfException("Native.load", "load SKF native library failed", ex);
|
||||||
|
}
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String stripExtension(String fileName) {
|
||||||
|
int index = fileName.lastIndexOf('.');
|
||||||
|
return index > 0 ? fileName.substring(0, index) : fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pointer connectDevice(String apiName, SkfLibrary lib, String deviceName) {
|
||||||
|
PointerByReference ref = new PointerByReference();
|
||||||
|
ensureSuccess(apiName + ":SKF_ConnectDev", lib.SKF_ConnectDev(cString(deviceName), ref));
|
||||||
|
return ref.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pointer openApplication(String apiName, SkfLibrary lib, Pointer devHandle, String applicationName) {
|
||||||
|
PointerByReference ref = new PointerByReference();
|
||||||
|
ensureSuccess(apiName + ":SKF_OpenApplication", lib.SKF_OpenApplication(devHandle, cString(applicationName), ref));
|
||||||
|
return ref.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pointer ensureApplication(String apiName, SkfLibrary lib, Pointer devHandle, String applicationName) {
|
||||||
|
PointerByReference ref = new PointerByReference();
|
||||||
|
int openRc = lib.SKF_OpenApplication(devHandle, cString(applicationName), ref);
|
||||||
|
if (openRc == 0) {
|
||||||
|
return ref.getValue();
|
||||||
|
}
|
||||||
|
if (openRc != SAR_APPLICATION_NOT_EXISTS) {
|
||||||
|
ensureSuccess(apiName + ":SKF_OpenApplication", openRc);
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticateDevice(apiName, lib, devHandle);
|
||||||
|
int createRc = lib.SKF_CreateApplication(
|
||||||
|
devHandle,
|
||||||
|
cString(applicationName),
|
||||||
|
cString(properties.getInitAdminPin()),
|
||||||
|
properties.getAdminPinRetryCount(),
|
||||||
|
cString(properties.getInitUserPin()),
|
||||||
|
properties.getUserPinRetryCount(),
|
||||||
|
properties.getCreateFileRights(),
|
||||||
|
ref
|
||||||
|
);
|
||||||
|
ensureSuccess(apiName + ":SKF_CreateApplication", createRc);
|
||||||
|
return ref.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pointer openContainer(String apiName, SkfLibrary lib, Pointer applicationHandle, String containerName) {
|
||||||
|
PointerByReference ref = new PointerByReference();
|
||||||
|
ensureSuccess(apiName + ":SKF_OpenContainer", lib.SKF_OpenContainer(applicationHandle, cString(containerName), ref));
|
||||||
|
return ref.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pointer ensureContainer(String apiName, SkfLibrary lib, Pointer applicationHandle, String containerName) {
|
||||||
|
PointerByReference ref = new PointerByReference();
|
||||||
|
int openRc = lib.SKF_OpenContainer(applicationHandle, cString(containerName), ref);
|
||||||
|
if (openRc == 0) {
|
||||||
|
return ref.getValue();
|
||||||
|
}
|
||||||
|
if (openRc != SAR_CONTAINER_NOT_EXISTS) {
|
||||||
|
ensureSuccess(apiName + ":SKF_OpenContainer", openRc);
|
||||||
|
}
|
||||||
|
|
||||||
|
int createRc = lib.SKF_CreateContainer(applicationHandle, cString(containerName), ref);
|
||||||
|
ensureSuccess(apiName + ":SKF_CreateContainer", createRc);
|
||||||
|
EccPublicKeyBlob publicKeyBlob = new EccPublicKeyBlob();
|
||||||
|
ensureSuccess(apiName + ":SKF_GenECCKeyPair",
|
||||||
|
lib.SKF_GenECCKeyPair(ref.getValue(), properties.getSignKeyAlgId(), publicKeyBlob));
|
||||||
|
return ref.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void authenticateDevice(String apiName, SkfLibrary lib, Pointer devHandle) {
|
||||||
|
DevInfo devInfo = new DevInfo();
|
||||||
|
ensureSuccess(apiName + ":SKF_GetDevInfo", lib.SKF_GetDevInfo(devHandle, devInfo));
|
||||||
|
devInfo.read();
|
||||||
|
|
||||||
|
byte[] random = new byte[16];
|
||||||
|
ensureSuccess(apiName + ":SKF_GenRandom", lib.SKF_GenRandom(devHandle, random, random.length));
|
||||||
|
|
||||||
|
PointerByReference keyHandleRef = new PointerByReference();
|
||||||
|
byte[] authKey = normalizeAuthKey(properties.getDeviceAuthKey(), devInfo.devAuthAlgId);
|
||||||
|
ensureSuccess(apiName + ":SKF_SetSymmKey",
|
||||||
|
lib.SKF_SetSymmKey(devHandle, authKey, devInfo.devAuthAlgId, keyHandleRef));
|
||||||
|
|
||||||
|
Pointer keyHandle = keyHandleRef.getValue();
|
||||||
|
try {
|
||||||
|
BlockCipherParam cipherParam = new BlockCipherParam();
|
||||||
|
cipherParam.ivLen = 0;
|
||||||
|
cipherParam.paddingType = 0;
|
||||||
|
cipherParam.feedBitLen = 0;
|
||||||
|
cipherParam.write();
|
||||||
|
|
||||||
|
ensureSuccess(apiName + ":SKF_EncryptInit", lib.SKF_EncryptInit(keyHandle, cipherParam));
|
||||||
|
|
||||||
|
byte[] encrypted = new byte[random.length + 32];
|
||||||
|
IntByReference encryptedLen = new IntByReference(encrypted.length);
|
||||||
|
ensureSuccess(apiName + ":SKF_Encrypt",
|
||||||
|
lib.SKF_Encrypt(keyHandle, random, random.length, encrypted, encryptedLen));
|
||||||
|
|
||||||
|
byte[] authData = new byte[encryptedLen.getValue()];
|
||||||
|
System.arraycopy(encrypted, 0, authData, 0, encryptedLen.getValue());
|
||||||
|
ensureSuccess(apiName + ":SKF_DevAuth", lib.SKF_DevAuth(devHandle, authData, authData.length));
|
||||||
|
} finally {
|
||||||
|
closeHandleQuietly(lib, keyHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] normalizeAuthKey(String configuredKey, int algId) {
|
||||||
|
byte[] raw = configuredKey.getBytes(StandardCharsets.UTF_8);
|
||||||
|
if (raw.length == 0) {
|
||||||
|
throw new SkfException("SKF_DevAuth", -1, "ukey.skf.device-auth-key must not be blank");
|
||||||
|
}
|
||||||
|
int keyLen = requiredKeyLength(algId);
|
||||||
|
if (raw.length == keyLen) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
byte[] normalized = new byte[keyLen];
|
||||||
|
for (int i = 0; i < keyLen; i++) {
|
||||||
|
normalized[i] = raw[i % raw.length];
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int requiredKeyLength(int algId) {
|
||||||
|
int family = algId & 0xFFFFFFF0;
|
||||||
|
if (family == SGD_AES256) {
|
||||||
|
return 32;
|
||||||
|
}
|
||||||
|
if (family == SGD_AES192 || family == SGD_T3DES) {
|
||||||
|
return 24;
|
||||||
|
}
|
||||||
|
if (family == SGD_SM4 || family == SGD_AES128 || family == SGD_SM1 || family == SGD_SM7 || family == SGD_D3DES) {
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
if (family == SGD_DES) {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disconnectQuietly(SkfLibrary lib, Pointer devHandle) {
|
||||||
|
if (devHandle != null) {
|
||||||
|
lib.SKF_DisConnectDev(devHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeApplicationQuietly(SkfLibrary lib, Pointer applicationHandle) {
|
||||||
|
if (applicationHandle != null) {
|
||||||
|
lib.SKF_CloseApplication(applicationHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeContainerQuietly(SkfLibrary lib, Pointer containerHandle) {
|
||||||
|
if (containerHandle != null) {
|
||||||
|
lib.SKF_CloseContainer(containerHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeHandleQuietly(SkfLibrary lib, Pointer handle) {
|
||||||
|
if (handle != null) {
|
||||||
|
lib.SKF_CloseHandle(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
server:
|
||||||
|
port: 8690
|
||||||
|
|
||||||
|
ukey:
|
||||||
|
skf:
|
||||||
|
create-file-rights: 255
|
||||||
|
sign-key-alg-id: 131328
|
||||||
Reference in New Issue
Block a user