我的原始答案显示了如何使用 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_P256
和SigningExamples-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
)