对 BouncyCastle 生成的 PDF 文档签名属性进行签名时,Azure Key Vault 签名失败

问题描述 投票:0回答:1

我们正在尝试使用 Azure Key Value 私钥来签署 BouncyCastle 为 PDF 文档生成的签名属性(嵌入 PDF 可哈希内容摘要)以允许 PDF 签名。

但是,使用 Azure Key Vault 的签名过程失败,因为 BouncyCastle 生成的签名属性长度不等于 32 个字节,并显示以下错误消息:

com.azure.security.keyvault.keys.implementation.models.KeyVaultErrorException: Status code 400, "{"error":{"code":"BadParameter","message":"Invalid length of 'value': 153 bytes. RS256 requires 32 bytes, encoded with base64url."}}"
    at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:733) ~[na:na]
    at com.azure.core.implementation.MethodHandleReflectiveInvoker.invokeStatic(MethodHandleReflectiveInvoker.java:26) ~[azure-core-1.51.0.jar:1.51.0]
    at com.azure.core.implementation.http.rest.ResponseExceptionConstructorCache.invoke(ResponseExceptionConstructorCache.java:53) ~[azure-core-1.51.0.jar:1.51.0]
    at com.azure.core.implementation.http.rest.RestProxyBase.instantiateUnexpectedException(RestProxyBase.java:407) ~[azure-core-1.51.0.jar:1.51.0]
    at com.azure.core.implementation.http.rest.SyncRestProxy.ensureExpectedStatus(SyncRestProxy.java:133) ~[azure-core-1.51.0.jar:1.51.0]
    at com.azure.core.implementation.http.rest.SyncRestProxy.handleRestReturnType(SyncRestProxy.java:211) ~[azure-core-1.51.0.jar:1.51.0]
    at com.azure.core.implementation.http.rest.SyncRestProxy.invoke(SyncRestProxy.java:86) ~[azure-core-1.51.0.jar:1.51.0]
    at com.azure.core.implementation.http.rest.RestProxyBase.invoke(RestProxyBase.java:124) ~[azure-core-1.51.0.jar:1.51.0]
    at com.azure.core.http.rest.RestProxy.invoke(RestProxy.java:95) ~[azure-core-1.51.0.jar:1.51.0]
    at jdk.proxy2/jdk.proxy2.$Proxy94.signSync(Unknown Source) ~[na:na]
    at com.azure.security.keyvault.keys.implementation.KeyClientImpl.signWithResponse(KeyClientImpl.java:3286) ~[azure-security-keyvault-keys-4.8.7.jar:4.8.7]
    at com.azure.security.keyvault.keys.cryptography.implementation.CryptographyClientImpl.sign(CryptographyClientImpl.java:248) ~[azure-security-keyvault-keys-4.8.7.jar:4.8.7]
    at com.azure.security.keyvault.keys.cryptography.implementation.RsaKeyCryptographyClient.sign(RsaKeyCryptographyClient.java:230) ~[azure-security-keyvault-keys-4.8.7.jar:4.8.7]
    at com.azure.security.keyvault.keys.cryptography.CryptographyClient.sign(CryptographyClient.java:699) ~[azure-security-keyvault-keys-4.8.7.jar:4.8.7]
    at com.azure.security.keyvault.keys.cryptography.CryptographyClient.sign(CryptographyClient.java:651) ~[azure-security-keyvault-keys-4.8.7.jar:4.8.7]
    at com.stackoverflow.keyvault.service.impl.AzureKeyVaultServiceImpl.signBytes(AzureKeyVaultServiceImpl.java:26) ~[classes/:na]
    at com.stackoverflow.keyvault.controller.KeyVaultController.signMoreByte32(KeyVaultController.java:51) ~[classes/:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:255) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[tomcat-embed-core-10.1.28.jar:6.0]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.28.jar:6.0]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.12.jar:6.1.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.12.jar:6.1.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.12.jar:6.1.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:384) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:904) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]

Azure Key Vault 不应该告诉我要签署哪些内容,不是吗?

相同的签名过程与本地自生成的私钥完美配合。但是,此类私钥不会存储在 HSM(硬件安全模块)中,并且可能会被 Adobe 撤销。

我们尝试利用 Azure Spring Boot 库利用 CryptoClient 实例来尝试签名过程。

application.yml

server:
    port: 8080
spring:
    application:
        name: Stackoverflow Key Vault
    cloud:
        azure:
            keyVault:
                url: https://my-vault.vault.azure.net/
                keyID: https://my-vault.vault.azure.net/keys/my-key

signature:
    algorithm: SHA256withRSA

KeyVaultController.java

@RestController
@RequestMapping("/api/vault")
@RequiredArgsConstructor
@Slf4j
public class KeyVaultController {
    private final KeyVaultService keyVaultService;

    @Value("${signature.algorithm}")
    private String signAlgo;

    @GetMapping("/sign-digest")
    public String signByte32() {
        // SHA-256 digest
        byte[] bytes = { -10, -106, 74, -128, 121, -80, 90, -100, 115, 82, -61, 120, -84, 23, 7, 5, 7, 96, -38, 95, -13, -46, 33, 70, -127, -66, 57, -31, 52, 123, -26, -109 };
        return Base64.getEncoder().encodeToString(keyVaultService.signBytes(signAlgo, bytes));
    }

    @GetMapping("/sign-pdf-digest")
    public String signMoreByte32() {
        // BouncyCastle signed attributes for PDF signing
        byte[] bytes = { 49, -127, -106, 48, 24, 6, 9, 42, -122, 72, -122, -9, 13, 1, 9, 3, 49, 11, 6, 9, 42, -122, 72, -122, -9, 13, 1, 7, 1, 48, 28, 6, 9, 42, -122, 72, -122, -9, 13, 1, 9, 5, 49, 15, 23, 13, 50, 52, 49, 49, 49, 52, 49, 48, 53, 57, 49, 51, 90, 48, 43, 6, 9, 42, -122, 72, -122, -9, 13, 1, 9, 52, 49, 30, 48, 28, 48, 11, 6, 9, 96, -122, 72, 1, 101, 3, 4, 2, 1, -95, 13, 6, 9, 42, -122, 72, -122, -9, 13, 1, 1, 11, 5, 0, 48, 47, 6, 9, 42, -122, 72, -122, -9, 13, 1, 9, 4, 49, 34, 4, 32, -29, -80, -60, 66, -104, -4, 28, 20, -102, -5, -12, -56, -103, 111, -71, 36, 39, -82, 65, -28, 100, -101, -109, 76, -92, -107, -103, 27, 120, 82, -72, 85 };
        return Base64.getEncoder().encodeToString(keyVaultService.signBytes(signAlgo, bytes));
    }

AzureKeyVaultServiceImpl.java

@Service
@RequiredArgsConstructor
public class AzureKeyVaultServiceImpl implements KeyVaultService {
    private final static Map<String, SignatureAlgorithm> ALGO_SIGNATURE_MAP = new HashMap<>();

    private final CryptographyClient cryptoClient;

    static {
        ALGO_SIGNATURE_MAP.put("SHA256withRSA", SignatureAlgorithm.RS256);
        ALGO_SIGNATURE_MAP.put("SHA384withRSA", SignatureAlgorithm.RS384);
        ALGO_SIGNATURE_MAP.put("SHA512withRSA", SignatureAlgorithm.RS512);
    }

    @Override
    public byte[] signBytes(String signAlgo, byte[] bytesToSign) {
        return cryptoClient.sign(ALGO_SIGNATURE_MAP.get(signAlgo), bytesToSign)
                .getSignature();
    }
}
java azure pdf digital-signature vault
1个回答
0
投票

首先,mkl正确地指出,必须使用CryptographyClient.signData而不是CryptographyClient.sign来对PDF签名属性进行签名,该属性总是超过32字节长。

经过一番调查,我们还得出以下结论,从而找到了解决签名无效问题的可行方案:

  1. Keys 对象永远无法与 Azure Key Vault 中的Certificates 对象匹配。这意味着使用 Keys 对象进行签名,同时在签名的 PDF 中嵌入下载的 Certificates 对象将始终失败,因为两个对象的公钥永远不会匹配!
  2. Certificates 对象始终使用隐藏的私钥创建,这意味着它也可用于对内容进行签名。此外,Certificates对象可以作为证书链下载并嵌入到签名的PDF中,并且其公钥与签名隐藏私钥的公钥相匹配。这其实就是解决问题的方法。
此外,还使用以下内容来使用 PDFBox 库实现 PDF 签名过程:

  1. https://github.com/apache/pdfbox/blob/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateSignatureBase.java
  2. 实现 org.bouncycastle.operator.ContentSigner 的自定义版本,以利用
  3. CryptographyClient.signData 方法使用 Azure Key Vault 进行签名。
© www.soinside.com 2019 - 2024. All rights reserved.