2

我正在尝试使用通过使用 AWS KMS 对我的 PdfDocument 的 SHA256 摘要进行签名而获得的签名,以在 PDF 本身上应用签名。我什至不确定我是否朝着正确的方向前进。

一切运行正常,但生成的文件的签名会引发错误:

Error during signature verification. ASN.1 parsing error:  Error encountered while BER decoding:

如果这很重要,我可以从 AWS 检索公钥,但私钥保留在他们身边。我在网上看到的大多数文档都预设了您对私钥的访问权限。此外,由于 AWS 处理签名,我不确定如何或从何处获取证书链。我发现的所有文档也都需要该证书链。

代码

首先,我创建了一个空的签名字段,因为大多数文档都指示您这样做。我认为可能存在问题,PdfName.Adbe_pkcs7_detached但如果这是错误的,我不知道还有什么可以代替它。

public void addEmptySignatureField(File src, File destination, String fieldName) throws IOException, GeneralSecurityException {
    try (
            var reader = new PdfReader(src);
            var output = new FileOutputStream(destination)
    ) {
        var signer = new PdfSigner(reader, output, new StampingProperties());

        signer.getSignatureAppearance()
                .setPageRect(new Rectangle(36, 748, 200, 100))
                .setPageNumber(1)
                .setLocation("whee")
                .setSignatureCreator("Mario")
                .setReason("because")
                .setLayer2FontSize(14f);
        signer.setFieldName(fieldName);

        IExternalSignatureContainer blankSignatureContainer = new ExternalBlankSignatureContainer(PdfName.Adobe_PPKLite,
                PdfName.Adbe_pkcs7_detached);

        // Sign the document using an blankSignatureContainer container.
        // 8192 is the size of the empty signature placeholder.
        signer.signExternalContainer(blankSignatureContainer, 8192);
    }
}

然后我尝试签署文件:

public void completeSignature(File src, File destination, String fieldName) throws IOException, GeneralSecurityException {
    try (
            var reader = new PdfReader(src);
            var pdfDocument = new PdfDocument(reader);
            var writer = new PdfWriter(destination)
    ) {
        // Signs a PDF where space was already reserved. The field must cover the whole document.
        PdfSigner.signDeferred(pdfDocument, fieldName, writer, kmsBackedSignatureContainer);
    }
}

作为参考,kmsBackedSignatureContainer 如下。 从其文档中定义的fileSigner.signAWS KMS a 返回:byte[]

此值是由 ANS X9.62–2005 和 RFC 3279 第 2.2.3 节定义的 DER 编码对象。

public class KmsBackedSignatureContainer implements IExternalSignatureContainer
{
    @Override
    public byte[] sign(InputStream data) throws GeneralSecurityException {
        try {
            var bytes = DigestAlgorithms.digest(data, new BouncyCastleDigest().getMessageDigest(DigestAlgorithms.SHA256));
            var derEncodedBytes = fileSigner.sign(bytes);

            return derEncodedBytes;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void modifySigningDictionary(PdfDictionary signDic)
    {
    }
}
4

2 回答 2

6

在此答案的上下文中,假设您已将凭据存储在文件default部分中,~/.aws/credentials并将您的区域存储在文件default部分中~/.aws/config。否则,您必须KmsClient在以下代码中调整实例化或初始化。

为 AWS KMS 密钥对生成证书

首先,AWS KMS 使用普通的非对称密钥对进行签名,它不为公钥提供 X.509 证书。但是,可互操作的 PDF 签名需要公钥的 X.509 证书才能建立对签名的信任。因此,可互操作的 AWS KMS PDF 签名的第一步是为您的 AWS KMS 签名密钥对的公钥生成 X.509 证书。

出于测试目的,您可以使用此帮助方法创建自签名证书,该方法基于此堆栈溢出答案中的代码:

public static Certificate generateSelfSignedCertificate(String keyId, String subjectDN) throws IOException, GeneralSecurityException {
    long now = System.currentTimeMillis();
    Date startDate = new Date(now);

    X500Name dnName = new X500Name(subjectDN);
    BigInteger certSerialNumber = new BigInteger(Long.toString(now));

    Calendar calendar = Calendar.getInstance();
    calendar.setTime(startDate);
    calendar.add(Calendar.YEAR, 1);

    Date endDate = calendar.getTime();

    PublicKey publicKey = null;
    SigningAlgorithmSpec signingAlgorithmSpec = null;
    try (   KmsClient kmsClient = KmsClient.create() ) {
        GetPublicKeyResponse response = kmsClient.getPublicKey(GetPublicKeyRequest.builder().keyId(keyId).build());
        SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(response.publicKey().asByteArray());
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
        publicKey = converter.getPublicKey(spki);
        List<SigningAlgorithmSpec> signingAlgorithms = response.signingAlgorithms();
        if (signingAlgorithms != null && !signingAlgorithms.isEmpty())
            signingAlgorithmSpec = signingAlgorithms.get(0);
    }
    JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(dnName, certSerialNumber, startDate, endDate, dnName, publicKey);

    ContentSigner contentSigner = new AwsKmsContentSigner(keyId, signingAlgorithmSpec);

    BasicConstraints basicConstraints = new BasicConstraints(true);
    certBuilder.addExtension(new ASN1ObjectIdentifier("2.5.29.19"), true, basicConstraints);

    return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certBuilder.build(contentSigner));
}

CertificateUtils辅助方法)

上面代码中使用的AwsKmsContentSigner类是 BouncyCastle 接口的这个实现ContentSigner

public class AwsKmsContentSigner implements ContentSigner {
    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    final String keyId;
    final SigningAlgorithmSpec signingAlgorithmSpec;
    final AlgorithmIdentifier signatureAlgorithm;

    public AwsKmsContentSigner(String keyId, SigningAlgorithmSpec signingAlgorithmSpec) {
        this.keyId = keyId;
        this.signingAlgorithmSpec = signingAlgorithmSpec;
        String signatureAlgorithmName = signingAlgorithmNameBySpec.get(signingAlgorithmSpec);
        if (signatureAlgorithmName == null)
            throw new IllegalArgumentException("Unknown signature algorithm " + signingAlgorithmSpec);
        this.signatureAlgorithm = new DefaultSignatureAlgorithmIdentifierFinder().find(signatureAlgorithmName);
    }

    @Override
    public byte[] getSignature() {
        try (   KmsClient kmsClient = KmsClient.create() ) {
            SignRequest signRequest = SignRequest.builder()
                    .signingAlgorithm(signingAlgorithmSpec)
                    .keyId(keyId)
                    .messageType(MessageType.RAW)
                    .message(SdkBytes.fromByteArray(outputStream.toByteArray()))
                    .build();
            SignResponse signResponse = kmsClient.sign(signRequest);
            SdkBytes signatureSdkBytes = signResponse.signature();
            return signatureSdkBytes.asByteArray();
        } finally {
            outputStream.reset();
        }
    }

    @Override
    public OutputStream getOutputStream() {
        return outputStream;
    }

    @Override
    public AlgorithmIdentifier getAlgorithmIdentifier() {
        return signatureAlgorithm;
    }

    final static Map<SigningAlgorithmSpec, String> signingAlgorithmNameBySpec;

    static {
        signingAlgorithmNameBySpec = new HashMap<>();
        signingAlgorithmNameBySpec.put(SigningAlgorithmSpec.ECDSA_SHA_256, "SHA256withECDSA");
        signingAlgorithmNameBySpec.put(SigningAlgorithmSpec.ECDSA_SHA_384, "SHA384withECDSA");
        signingAlgorithmNameBySpec.put(SigningAlgorithmSpec.ECDSA_SHA_512, "SHA512withECDSA");
        signingAlgorithmNameBySpec.put(SigningAlgorithmSpec.RSASSA_PKCS1_V1_5_SHA_256, "SHA256withRSA");
        signingAlgorithmNameBySpec.put(SigningAlgorithmSpec.RSASSA_PKCS1_V1_5_SHA_384, "SHA384withRSA");
        signingAlgorithmNameBySpec.put(SigningAlgorithmSpec.RSASSA_PKCS1_V1_5_SHA_512, "SHA512withRSA");
        signingAlgorithmNameBySpec.put(SigningAlgorithmSpec.RSASSA_PSS_SHA_256, "SHA256withRSAandMGF1");
        signingAlgorithmNameBySpec.put(SigningAlgorithmSpec.RSASSA_PSS_SHA_384, "SHA384withRSAandMGF1");
        signingAlgorithmNameBySpec.put(SigningAlgorithmSpec.RSASSA_PSS_SHA_512, "SHA512withRSAandMGF1");
    }
}

( AwsKmsContentSigner )

出于生产目的,您通常需要使用由受信任的 CA 签名的证书。与上述类似,您可以为您的 AWS KMS 公钥创建和签署证书请求,将其发送到您选择的 CA,然后从他们那里取回要使用的证书。

使用 AWS KMS 密钥对签署 PDF

要使用 iText 签署 PDF,您需要实现 iTextIExternalSignatureIExternalSignatureContainer界面。这里我们使用前者:

public class AwsKmsSignature implements IExternalSignature {
    public AwsKmsSignature(String keyId) {
        this.keyId = keyId;

        try (   KmsClient kmsClient = KmsClient.create() ) {
            GetPublicKeyRequest getPublicKeyRequest = GetPublicKeyRequest.builder()
                    .keyId(keyId)
                    .build();
            GetPublicKeyResponse getPublicKeyResponse = kmsClient.getPublicKey(getPublicKeyRequest);
            signingAlgorithmSpec = getPublicKeyResponse.signingAlgorithms().get(0);
            switch(signingAlgorithmSpec) {
            case ECDSA_SHA_256:
            case ECDSA_SHA_384:
            case ECDSA_SHA_512:
            case RSASSA_PKCS1_V1_5_SHA_256:
            case RSASSA_PKCS1_V1_5_SHA_384:
            case RSASSA_PKCS1_V1_5_SHA_512:
                break;
            case RSASSA_PSS_SHA_256:
            case RSASSA_PSS_SHA_384:
            case RSASSA_PSS_SHA_512:
                throw new IllegalArgumentException(String.format("Signing algorithm %s not supported directly by iText", signingAlgorithmSpec));
            default:
                throw new IllegalArgumentException(String.format("Unknown signing algorithm: %s", signingAlgorithmSpec));
            }
        }
    }

    @Override
    public String getHashAlgorithm() {
        switch(signingAlgorithmSpec) {
        case ECDSA_SHA_256:
        case RSASSA_PKCS1_V1_5_SHA_256:
            return "SHA-256";
        case ECDSA_SHA_384:
        case RSASSA_PKCS1_V1_5_SHA_384:
            return "SHA-384";
        case ECDSA_SHA_512:
        case RSASSA_PKCS1_V1_5_SHA_512:
            return "SHA-512";
        default:
            return null;
        }
    }

    @Override
    public String getEncryptionAlgorithm() {
        switch(signingAlgorithmSpec) {
        case ECDSA_SHA_256:
        case ECDSA_SHA_384:
        case ECDSA_SHA_512:
            return "ECDSA";
        case RSASSA_PKCS1_V1_5_SHA_256:
        case RSASSA_PKCS1_V1_5_SHA_384:
        case RSASSA_PKCS1_V1_5_SHA_512:
            return "RSA";
        default:
            return null;
        }
    }

    @Override
    public byte[] sign(byte[] message) throws GeneralSecurityException {
        try (   KmsClient kmsClient = KmsClient.create() ) {
            SignRequest signRequest = SignRequest.builder()
                    .signingAlgorithm(signingAlgorithmSpec)
                    .keyId(keyId)
                    .messageType(MessageType.RAW)
                    .message(SdkBytes.fromByteArray(message))
                    .build();
            SignResponse signResponse = kmsClient.sign(signRequest);
            return signResponse.signature().asByteArray();
        }
    }

    final String keyId;
    final SigningAlgorithmSpec signingAlgorithmSpec;
}

( AwsKmsSignature )

在构造函数中,我们选择可用于相关密钥的签名算法。这实际上是在这里非常随意地完成的,而不是简单地采用第一个算法,您可能想要强制使用特定的散列算法。

getHashAlgorithmgetEncryptionAlgorithm返回签名算法的各个部分的名称,并sign简单地创建一个签名。

付诸行动

假设您的 AWS KMS 签名密钥对具有别名SigningExamples-ECC_NIST_P256,您可以使用上面的代码对 PDF 进行签名:

BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);

String keyId = "alias/SigningExamples-ECC_NIST_P256";
AwsKmsSignature signature = new AwsKmsSignature(keyId);
Certificate certificate = CertificateUtils.generateSelfSignedCertificate(keyId, "CN=AWS KMS PDF Signing Test,OU=mkl tests,O=mkl");

try (   PdfReader pdfReader = new PdfReader(PDF_TO_SIGN);
        OutputStream result = new FileOutputStream(SIGNED_PDF)) {
    PdfSigner pdfSigner = new PdfSigner(pdfReader, result, new StampingProperties().useAppendMode());

    IExternalDigest externalDigest = new BouncyCastleDigest();
    pdfSigner.signDetached(externalDigest , signature, new Certificate[] {certificate}, null, null, null, 0, CryptoStandard.CMS);
}

TestSign简单测试testSignSimpleEcdsa

重新访问使用 AWS KMS 密钥对签署 PDF

上面我们使用了IExternalSignaturefor 签名的实现。虽然这是最简单的方法,但它有一些缺点:PdfPKCS7在这种情况下使用的类不支持 RSASSA-PSS 使用,并且对于 ECDSA 签名,它使用错误的 OID 作为签名算法 OID。

为了不受这些问题的影响,我们在这里使用了一个实现,IExternalSignatureContainer我们只使用 BouncyCastle 功能自己构建完整的 CMS 签名容器。

public class AwsKmsSignatureContainer implements IExternalSignatureContainer {
    public AwsKmsSignatureContainer(X509Certificate x509Certificate, String keyId) {
        this(x509Certificate, keyId, a -> a != null && a.size() > 0 ? a.get(0) : null);
    }

    public AwsKmsSignatureContainer(X509Certificate x509Certificate, String keyId, Function<List<SigningAlgorithmSpec>, SigningAlgorithmSpec> selector) {
        this.x509Certificate = x509Certificate;
        this.keyId = keyId;

        try (   KmsClient kmsClient = KmsClient.create() ) {
            GetPublicKeyRequest getPublicKeyRequest = GetPublicKeyRequest.builder()
                    .keyId(keyId)
                    .build();
            GetPublicKeyResponse getPublicKeyResponse = kmsClient.getPublicKey(getPublicKeyRequest);
            signingAlgorithmSpec = selector.apply(getPublicKeyResponse.signingAlgorithms());
            if (signingAlgorithmSpec == null)
                throw new IllegalArgumentException("KMS key has no signing algorithms");
            contentSigner = new AwsKmsContentSigner(keyId, signingAlgorithmSpec);
        }
    }

    @Override
    public byte[] sign(InputStream data) throws GeneralSecurityException {
        try {
            CMSTypedData msg = new CMSTypedDataInputStream(data);

            X509CertificateHolder signCert = new X509CertificateHolder(x509Certificate.getEncoded());

            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

            gen.addSignerInfoGenerator(
                    new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
                            .build(contentSigner, signCert));

            gen.addCertificates(new JcaCertStore(Collections.singleton(signCert)));

            CMSSignedData sigData = gen.generate(msg, false);
            return sigData.getEncoded();
        } catch (IOException | OperatorCreationException | CMSException e) {
            throw new GeneralSecurityException(e);
        }
    }

    @Override
    public void modifySigningDictionary(PdfDictionary signDic) {
        signDic.put(PdfName.Filter, new PdfName("MKLx_AWS_KMS_SIGNER"));
        signDic.put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached);
    }

    final X509Certificate x509Certificate;
    final String keyId;
    final SigningAlgorithmSpec signingAlgorithmSpec;
    final ContentSigner contentSigner;

    class CMSTypedDataInputStream implements CMSTypedData {
        InputStream in;

        public CMSTypedDataInputStream(InputStream is) {
            in = is;
        }

        @Override
        public ASN1ObjectIdentifier getContentType() {
            return PKCSObjectIdentifiers.data;
        }

        @Override
        public Object getContent() {
            return in;
        }

        @Override
        public void write(OutputStream out) throws IOException,
                CMSException {
            byte[] buffer = new byte[8 * 1024];
            int read;
            while ((read = in.read(buffer)) != -1) {
                out.write(buffer, 0, read);
            }
            in.close();
        }
    }
}

( AwsKmsSignatureContainer )

在构造函数中,我们还选择了可用于相关密钥的签名算法。但是,在这里,我们允许一个函数参数允许调用者在可用的签名算法中进行选择。这对于 RSASSA-PSS 的使用尤其必要。

重新审视将其付诸行动

假设您有一个 AWS KMS 签名 RSA_2048 密钥对,该密钥对具有别名 SigningExamples-RSA_2048,您可以使用上面的代码,使用 RSASSA-PSS 对 PDF 进行签名:

BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);

String keyId = "alias/SigningExamples-RSA_2048";
X509Certificate certificate = CertificateUtils.generateSelfSignedCertificate(keyId, "CN=AWS KMS PDF Signing Test,OU=mkl tests,O=mkl");
AwsKmsSignatureContainer signatureContainer = new AwsKmsSignatureContainer(certificate, keyId, TestSignSimple::selectRsaSsaPss);

try (   PdfReader pdfReader = new PdfReader(PDF_TO_SIGN);
        OutputStream result = new FileOutputStream(SIGNED_PDF)) {
    PdfSigner pdfSigner = new PdfSigner(pdfReader, result, new StampingProperties().useAppendMode());

    pdfSigner.signExternalContainer(signatureContainer, 8192);
}

TestSign简单测试testSignSimpleRsaSsaPss

使用此选择器功能

static SigningAlgorithmSpec selectRsaSsaPss (List<SigningAlgorithmSpec> specs) {
    if (specs != null)
        return specs.stream().filter(spec -> spec.toString().startsWith("RSASSA_PSS")).findFirst().orElse(null);
    else
        return null;
}

TestSignSimple辅助方法)

批量签名注意事项

如果您计划使用 AWS KMS 进行批量签名,请注意 AWS KMS 为其某些操作建立的请求配额:

配额名称 默认值(每秒)
加密操作 (RSA) 请求率 RSA CMK 为 500(共享)
加密操作 (ECC) 请求率 300(共享)用于椭圆曲线 (ECC) CMK
GetPublicKey 请求率 5

(摘自“AWS Key Management Service Developer Guide”/“配额”/“请求配额”/ “请求每个 AWS KMS API 操作的配额”查看时间:2020 年 12 月 15 日)

RSAECC 加密操作请求率可能不是问题。或者更重要的是,如果它们有问题,AWS KMS 很可能不是满足您需求的正确签名产品;相反,您应该寻找实际的 HSM,无论是物理的还是作为服务的,例如AWS CloudHSM

另一方面,GetPublicKey 请求率很可能是个问题:两者及其各自AwsKmsSignatureAwsKmsSignatureContainer构造函数都调用该方法。因此,基于它们的天真的大规模签名代码将被限制为每秒 5 个签名。

根据您的用例,有不同的策略来解决这个问题。

如果您的签名代码只有极少数实例同时运行并且它们仅使用极少数不同的密钥,您可以简单地重用您的AwsKmsSignatureAwsKmsSignatureContainer对象,在启动时或按需创建它们,然后缓存它们。

但是,否则,您应该从AwsKmsSignatureandAwsKmsSignatureContainer构造函数中重构 GetPublicKey 方法的使用。它仅用于确定在使用相关密钥进行签名时要使用的 AWS KMS 签名算法标识符。显然,您可以将该标识符与密钥标识符一起存储,从而无需调用 GetPublicKey。

于 2020-12-08T17:40:25.513 回答
2

我的原始答案显示了如何使用 AWS KMS 和 iText 7 for Java 签署 PDF。为了完整起见,我将其移植到.Net。由于我使用真正的 .Net 类来创建自签名证书,并且它们的 Java 和 .Net 版本之间的 AWS KMS 和 BouncyCastle API 存在一些差异,因此代码不仅在方法名称大写方面有所不同......

证书实用程序

.Net 为创建证书请求和自签名证书类提供了自己的方法CertificateRequest

与另一个答案中的 BouncyCastle/Java 实现类似,这个类也有实际的签名创建(对于自签名证书)委托给一个助手,这里是一个X509SignatureGenerator实例。显然 .Net 没有该类的现成变体用于 AWS KMS 签名,因此我们必须自己提供一个,即SignatureGenerator下面代码中的内部类。幸运的是,我们可以重复使用X509SignatureGenerator除实际签名方法之外的所有方法的.Net 变体SignData

public static X509Certificate2 generateSelfSignedCertificate(string keyId, string subjectDN, Func<List<string>, string> selector)
{
    string signingAlgorithm = null;
    using (var kmsClient = new AmazonKeyManagementServiceClient())
    {
        GetPublicKeyRequest getPublicKeyRequest = new GetPublicKeyRequest() { KeyId = keyId };
        GetPublicKeyResponse getPublicKeyResponse = kmsClient.GetPublicKeyAsync(getPublicKeyRequest).Result;
        List<string> signingAlgorithms = getPublicKeyResponse.SigningAlgorithms;
        signingAlgorithm = selector.Invoke(signingAlgorithms);
        byte[] spkiBytes = getPublicKeyResponse.PublicKey.ToArray();

        CertificateRequest certificateRequest = null;
        X509SignatureGenerator simpleGenerator = null;
        string keySpecString = getPublicKeyResponse.CustomerMasterKeySpec.ToString();
        if (keySpecString.StartsWith("ECC"))
        {
            ECDsa ecdsa = ECDsa.Create();
            int bytesRead = 0;
            ecdsa.ImportSubjectPublicKeyInfo(new ReadOnlySpan<byte>(spkiBytes), out bytesRead);
            certificateRequest = new CertificateRequest(subjectDN, ecdsa, getHashAlgorithmName(signingAlgorithm));
            simpleGenerator = X509SignatureGenerator.CreateForECDsa(ecdsa);
        }
        else if (keySpecString.StartsWith("RSA"))
        {
            RSA rsa = RSA.Create();
            int bytesRead = 0;
            rsa.ImportSubjectPublicKeyInfo(new ReadOnlySpan<byte>(spkiBytes), out bytesRead);
            RSASignaturePadding rsaSignaturePadding = getSignaturePadding(signingAlgorithm);
            certificateRequest = new CertificateRequest(subjectDN, rsa, getHashAlgorithmName(signingAlgorithm), rsaSignaturePadding);
            simpleGenerator = X509SignatureGenerator.CreateForRSA(rsa, rsaSignaturePadding);
        }
        else
        {
            throw new ArgumentException("Cannot determine encryption algorithm for " + keySpecString, nameof(keyId));
        }

        X509SignatureGenerator generator = new SignatureGenerator(keyId, signingAlgorithm, simpleGenerator);
        X509Certificate2 certificate = certificateRequest.Create(new X500DistinguishedName(subjectDN), generator, System.DateTimeOffset.Now, System.DateTimeOffset.Now.AddYears(2), new byte[] { 17 });
        return certificate;
    }
}

public static HashAlgorithmName getHashAlgorithmName(string signingAlgorithm)
{
    if (signingAlgorithm.Contains("SHA_256"))
    {
        return HashAlgorithmName.SHA256;
    }
    else if (signingAlgorithm.Contains("SHA_384"))
    {
        return HashAlgorithmName.SHA384;
    }
    else if (signingAlgorithm.Contains("SHA_512"))
    {
        return HashAlgorithmName.SHA512;
    }
    else
    {
        throw new ArgumentException("Cannot determine hash algorithm for " + signingAlgorithm, nameof(signingAlgorithm));
    }
}

public static RSASignaturePadding getSignaturePadding(string signingAlgorithm)
{
    if (signingAlgorithm.StartsWith("RSASSA_PKCS1_V1_5"))
    {
        return RSASignaturePadding.Pkcs1;
    }
    else if (signingAlgorithm.StartsWith("RSASSA_PSS"))
    {
        return RSASignaturePadding.Pss;
    }
    else
    {
        return null;
    }
}

class SignatureGenerator : X509SignatureGenerator
{
    public SignatureGenerator(string keyId, string signingAlgorithm, X509SignatureGenerator simpleGenerator)
    {
        this.keyId = keyId;
        this.signingAlgorithm = signingAlgorithm;
        this.simpleGenerator = simpleGenerator;
    }

    public override byte[] GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlgorithm)
    {
        HashAlgorithmName hashAlgorithmHere = getHashAlgorithmName(signingAlgorithm);
        if (hashAlgorithm != hashAlgorithmHere)
        {
            throw new ArgumentException("Hash algorithm " + hashAlgorithm + "does not match signing algorithm " + signingAlgorithm, nameof(hashAlgorithm));
        }
        return simpleGenerator.GetSignatureAlgorithmIdentifier(hashAlgorithm);
    }

    public override byte[] SignData(byte[] data, HashAlgorithmName hashAlgorithm)
    {
        HashAlgorithmName hashAlgorithmHere = getHashAlgorithmName(signingAlgorithm);
        if (hashAlgorithm != hashAlgorithmHere)
        {
            throw new ArgumentException("Hash algorithm " + hashAlgorithm + "does not match signing algorithm " + signingAlgorithm, nameof(hashAlgorithm));
        }

        using (var kmsClient = new AmazonKeyManagementServiceClient())
        {
            SignRequest signRequest = new SignRequest()
            {
                SigningAlgorithm = signingAlgorithm,
                KeyId = keyId,
                MessageType = MessageType.RAW,
                Message = new MemoryStream(data)
            };
            SignResponse signResponse = kmsClient.SignAsync(signRequest).Result;
            return signResponse.Signature.ToArray();
        }
    }

    protected override PublicKey BuildPublicKey()
    {
        return simpleGenerator.PublicKey;
    }

    string keyId;
    string signingAlgorithm;
    X509SignatureGenerator simpleGenerator;
}

( CertificateUtils.cs )

AwsKms 签名

该类AwsKmsSignature可以从 Java 移植,只需很少的更改。

public class AwsKmsSignature : IExternalSignature
{
    public AwsKmsSignature(string keyId, Func<List<string>, string> selector)
    {
        this.keyId = keyId;
        using (var kmsClient = new AmazonKeyManagementServiceClient())
        {
            GetPublicKeyRequest getPublicKeyRequest = new GetPublicKeyRequest() { KeyId = keyId };
            GetPublicKeyResponse getPublicKeyResponse = kmsClient.GetPublicKeyAsync(getPublicKeyRequest).Result;
            List<string> signingAlgorithms = getPublicKeyResponse.SigningAlgorithms;
            signingAlgorithm = selector.Invoke(signingAlgorithms);
            switch(signingAlgorithm)
            {
                case "ECDSA_SHA_256":
                case "ECDSA_SHA_384":
                case "ECDSA_SHA_512":
                case "RSASSA_PKCS1_V1_5_SHA_256":
                case "RSASSA_PKCS1_V1_5_SHA_384":
                case "RSASSA_PKCS1_V1_5_SHA_512":
                    break;
                case "RSASSA_PSS_SHA_256":
                case "RSASSA_PSS_SHA_384":
                case "RSASSA_PSS_SHA_512":
                    throw new ArgumentException(String.Format("Signing algorithm {0} not supported directly by iText", signingAlgorithm));
                default:
                    throw new ArgumentException(String.Format("Unknown signing algorithm: {0}", signingAlgorithm));
            }
        }
    }

    public string GetEncryptionAlgorithm()
    {
        switch (signingAlgorithm)
        {
            case "ECDSA_SHA_256":
            case "ECDSA_SHA_384":
            case "ECDSA_SHA_512":
                return "ECDSA";
            case "RSASSA_PKCS1_V1_5_SHA_256":
            case "RSASSA_PKCS1_V1_5_SHA_384":
            case "RSASSA_PKCS1_V1_5_SHA_512":
                return "RSA";
            default:
                return null;
        }
    }

    public string GetHashAlgorithm()
    {
        switch (signingAlgorithm)
        {
            case "ECDSA_SHA_256":
            case "RSASSA_PKCS1_V1_5_SHA_256":
                return "SHA-256";
            case "ECDSA_SHA_384":
            case "RSASSA_PKCS1_V1_5_SHA_384":
                return "SHA-384";
            case "ECDSA_SHA_512":
            case "RSASSA_PKCS1_V1_5_SHA_512":
                return "SHA-512";
            default:
                return null;
        }
    }

    public byte[] Sign(byte[] message)
    {
        using (var kmsClient = new AmazonKeyManagementServiceClient())
        {
            SignRequest signRequest = new SignRequest() {
                SigningAlgorithm = signingAlgorithm,
                KeyId=keyId,
                MessageType=MessageType.RAW,
                Message=new MemoryStream(message)
            };
            SignResponse signResponse = kmsClient.SignAsync(signRequest).Result;
            return signResponse.Signature.ToArray();
        }
    }

    string keyId;
    string signingAlgorithm;
}

( AwsKmsSignature.cs )

AwsKmsSignatureContainer

该类AwsKmsSignatureContainer使用 BouncyCastle 构建要嵌入的 CMS 签名容器,就像在另一个答案中的 Java 版本中一样。

不过,BouncyCastle API 存在某些差异。特别是,不使用 的实例ContentSigner进行实际签名,而是使用 ; 的实例ISignatureFactory。该接口代表了一个实例工厂,这些实例的功能是JavaIStreamCalculator中的实际挂件。ContentSigner这些接口的实现AwsKmsSignatureFactory如下AwsKmsStreamCalculator

public class AwsKmsSignatureContainer : IExternalSignatureContainer
{
    public AwsKmsSignatureContainer(X509Certificate x509Certificate, string keyId, Func<List<string>, string> selector)
    {
        this.x509Certificate = x509Certificate;
        this.keyId = keyId;

        using (var kmsClient = new AmazonKeyManagementServiceClient())
        {
            GetPublicKeyRequest getPublicKeyRequest = new GetPublicKeyRequest() { KeyId = keyId };
            GetPublicKeyResponse getPublicKeyResponse = kmsClient.GetPublicKeyAsync(getPublicKeyRequest).Result;
            List<string> signingAlgorithms = getPublicKeyResponse.SigningAlgorithms;
            this.signingAlgorithm = selector.Invoke(signingAlgorithms);
            if (signingAlgorithm == null)
                throw new ArgumentException("KMS key has no signing algorithms", nameof(keyId));
            signatureFactory = new AwsKmsSignatureFactory(keyId, signingAlgorithm);
        }
    }

    public void ModifySigningDictionary(PdfDictionary signDic)
    {
        signDic.Put(PdfName.Filter, new PdfName("MKLx_AWS_KMS_SIGNER"));
        signDic.Put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached);
    }

    public byte[] Sign(Stream data)
    {
        CmsProcessable msg = new CmsProcessableInputStream(data);

        CmsSignedDataGenerator gen = new CmsSignedDataGenerator();

        SignerInfoGenerator signerInfoGenerator = new SignerInfoGeneratorBuilder()
            .WithSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator())
            .Build(signatureFactory, x509Certificate);
        gen.AddSignerInfoGenerator(signerInfoGenerator);

        X509CollectionStoreParameters collectionStoreParameters = new X509CollectionStoreParameters(new List<X509Certificate> { x509Certificate });
        IX509Store collectionStore = X509StoreFactory.Create("CERTIFICATE/COLLECTION", collectionStoreParameters);
        gen.AddCertificates(collectionStore);

        CmsSignedData sigData = gen.Generate(msg, false);
        return sigData.GetEncoded();
    }

    X509Certificate x509Certificate;
    String keyId;
    string signingAlgorithm;
    ISignatureFactory signatureFactory;
}

class AwsKmsSignatureFactory : ISignatureFactory
{
    private string keyId;
    private string signingAlgorithm;
    private AlgorithmIdentifier signatureAlgorithm;

    public AwsKmsSignatureFactory(string keyId, string signingAlgorithm)
    {
        this.keyId = keyId;
        this.signingAlgorithm = signingAlgorithm;
        string signatureAlgorithmName = signingAlgorithmNameBySpec[signingAlgorithm];
        if (signatureAlgorithmName == null)
            throw new ArgumentException("Unknown signature algorithm " + signingAlgorithm, nameof(signingAlgorithm));

        // Special treatment because of issue https://github.com/bcgit/bc-csharp/issues/250
        switch (signatureAlgorithmName.ToUpperInvariant())
        {
            case "SHA256WITHECDSA":
                this.signatureAlgorithm = new AlgorithmIdentifier(X9ObjectIdentifiers.ECDsaWithSha256);
                break;
            case "SHA512WITHECDSA":
                this.signatureAlgorithm = new AlgorithmIdentifier(X9ObjectIdentifiers.ECDsaWithSha512);
                break;
            default:
                this.signatureAlgorithm = new DefaultSignatureAlgorithmIdentifierFinder().Find(signatureAlgorithmName);
                break;
        }
    }

    public object AlgorithmDetails => signatureAlgorithm;

    public IStreamCalculator CreateCalculator()
    {
        return new AwsKmsStreamCalculator(keyId, signingAlgorithm);
    }

    static Dictionary<string, string> signingAlgorithmNameBySpec = new Dictionary<string, string>()
    {
        { "ECDSA_SHA_256", "SHA256withECDSA" },
        { "ECDSA_SHA_384", "SHA384withECDSA" },
        { "ECDSA_SHA_512", "SHA512withECDSA" },
        { "RSASSA_PKCS1_V1_5_SHA_256", "SHA256withRSA" },
        { "RSASSA_PKCS1_V1_5_SHA_384", "SHA384withRSA" },
        { "RSASSA_PKCS1_V1_5_SHA_512", "SHA512withRSA" },
        { "RSASSA_PSS_SHA_256", "SHA256withRSAandMGF1"},
        { "RSASSA_PSS_SHA_384", "SHA384withRSAandMGF1"},
        { "RSASSA_PSS_SHA_512", "SHA512withRSAandMGF1"}
    };
}

class AwsKmsStreamCalculator : IStreamCalculator
{
    private string keyId;
    private string signingAlgorithm;
    private MemoryStream stream = new MemoryStream();

    public AwsKmsStreamCalculator(string keyId, string signingAlgorithm)
    {
        this.keyId = keyId;
        this.signingAlgorithm = signingAlgorithm;
    }

    public Stream Stream => stream;

    public object GetResult()
    {
        try
        {
            using (var kmsClient = new AmazonKeyManagementServiceClient())
            {
                SignRequest signRequest = new SignRequest()
                {
                    SigningAlgorithm = signingAlgorithm,
                    KeyId = keyId,
                    MessageType = MessageType.RAW,
                    Message = new MemoryStream(stream.ToArray())
                };
                SignResponse signResponse = kmsClient.SignAsync(signRequest).Result;
                return new SimpleBlockResult(signResponse.Signature.ToArray());
            }
        }
        finally
        {
            stream = new MemoryStream();
        }
    }
}

( AwsKmsSignatureContainer.cs )

付诸行动

与另一个答案中关于凭证和默认区域的配置相同的先决条件,可以使用上面的类使用 iText for .Net 使用 AWS KMS 密钥签署 PDF,如下所示,假设一个 AWS KMS 签名密钥对具有别名SigningExamples-ECC_NIST_P256SigningExamples-RSA_2048分别匹配他们的算法。

ECDSA 签署

string keyId = "alias/SigningExamples-ECC_NIST_P256";
Func<System.Collections.Generic.List<string>, string> selector = list => list.Find(name => name.StartsWith("ECDSA_SHA_256"));
AwsKmsSignature signature = new AwsKmsSignature(keyId, selector);
System.Security.Cryptography.X509Certificates.X509Certificate2 certificate2 = CertificateUtils.generateSelfSignedCertificate(
    keyId,
    "CN=AWS KMS PDF Signing Test ECDSA,OU=mkl tests,O=mkl",
    selector
);
X509Certificate certificate = new X509Certificate(X509CertificateStructure.GetInstance(certificate2.RawData));

using (PdfReader pdfReader = new PdfReader(PDF_TO_SIGN))
using (FileStream result = File.Create(SIGNED_PDF))
{
    PdfSigner pdfSigner = new PdfSigner(pdfReader, result, new StampingProperties().UseAppendMode());

    pdfSigner.SignDetached(signature, new X509Certificate[] { certificate }, null, null, null, 0, CryptoStandard.CMS);
}

TestSignSimple.cs测试testSignSimpleEcdsa

RSA(PKCS#1 v1.5 填充)签名

string keyId = "alias/SigningExamples-RSA_2048";
Func<System.Collections.Generic.List<string>, string> selector = list => list.Find(name => name.StartsWith("RSASSA_PKCS1_V1_5"));
AwsKmsSignature signature = new AwsKmsSignature(keyId, selector);
System.Security.Cryptography.X509Certificates.X509Certificate2 certificate2 = CertificateUtils.generateSelfSignedCertificate(
    keyId,
    "CN=AWS KMS PDF Signing Test RSA,OU=mkl tests,O=mkl",
    selector
);
X509Certificate certificate = new X509Certificate(X509CertificateStructure.GetInstance(certificate2.RawData));

using (PdfReader pdfReader = new PdfReader(PDF_TO_SIGN))
using (FileStream result = File.Create(SIGNED_PDF))
{
    PdfSigner pdfSigner = new PdfSigner(pdfReader, result, new StampingProperties().UseAppendMode());

    pdfSigner.SignDetached(signature, new X509Certificate[] { certificate }, null, null, null, 0, CryptoStandard.CMS);
} 

TestSignSimple.cs测试testSignSimpleRsa

RSASSA-PSS 签名

string keyId = "alias/SigningExamples-RSA_2048";
Func<System.Collections.Generic.List<string>, string> selector = list => list.Find(name => name.StartsWith("RSASSA_PSS"));
System.Security.Cryptography.X509Certificates.X509Certificate2 certificate2 = CertificateUtils.generateSelfSignedCertificate(
    keyId,
    "CN=AWS KMS PDF Signing Test RSAwithMGF1,OU=mkl tests,O=mkl",
    selector
);
X509Certificate certificate = new X509Certificate(X509CertificateStructure.GetInstance(certificate2.RawData));
AwsKmsSignatureContainer signature = new AwsKmsSignatureContainer(certificate, keyId, selector);

using (PdfReader pdfReader = new PdfReader(PDF_TO_SIGN))
using (FileStream result = File.Create(SIGNED_PDF))
{
    PdfSigner pdfSigner = new PdfSigner(pdfReader, result, new StampingProperties().UseAppendMode());

    pdfSigner.SignExternalContainer(signature, 8192);
} 

TestSignSimple.cs测试testSignSimpleRsaSsaPss

于 2021-01-22T11:48:23.907 回答