Using iText and AWS KMS to digitally sign a PDF document: Part 1
This example was written for the article "Using iText and AWS KMS to digitally sign a PDF document" and shows how to generate a self-signed certificate for testing purposes, which you can use with an AWS KMS Key Pair.
For production purposes however, you'll usually want to use a certificate signed by a trusted Certificate Authority (CA). You can do this in a similar way to that shown in this example, by creating and signing a certificate request for your AWS KMS public key, sending it to your CA of choice, and getting back the certificate to use from them.
CertificateUtils
package com.itextpdf.signingexamples.aws.kms;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.function.Function;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.operator.ContentSigner;
import software.amazon.awssdk.services.kms.KmsClient;
import software.amazon.awssdk.services.kms.model.GetPublicKeyRequest;
import software.amazon.awssdk.services.kms.model.GetPublicKeyResponse;
import software.amazon.awssdk.services.kms.model.SigningAlgorithmSpec;
/**
* @author mkl
*/
public class CertificateUtils {
public static X509Certificate generateSelfSignedCertificate(String keyId, String subjectDN) throws IOException, GeneralSecurityException {
return generateSelfSignedCertificate(keyId, subjectDN, a -> a != null && a.size() > 0 ? a.get(0) : null);
}
// based on https://stackoverflow.com/a/43918337/1729265
public static X509Certificate generateSelfSignedCertificate(String keyId, String subjectDN, Function<List<SigningAlgorithmSpec>, SigningAlgorithmSpec> selector) throws IOException, GeneralSecurityException {
long now = System.currentTimeMillis();
Date startDate = new Date(now);
X500Name dnName = new X500Name(subjectDN);
BigInteger certSerialNumber = new BigInteger(Long.toString(now)); // <-- Using the current timestamp as the certificate serial number
Calendar calendar = Calendar.getInstance();
calendar.setTime(startDate);
calendar.add(Calendar.YEAR, 1); // <-- 1 Yr validity
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();
signingAlgorithmSpec = selector.apply(signingAlgorithms);
}
JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(dnName, certSerialNumber, startDate, endDate, dnName, publicKey);
ContentSigner contentSigner = new AwsKmsContentSigner(keyId, signingAlgorithmSpec);
// Extensions --------------------------
// Basic Constraints
BasicConstraints basicConstraints = new BasicConstraints(true); // <-- true for CA, false for EndEntity
certBuilder.addExtension(new ASN1ObjectIdentifier("2.5.29.19"), true, basicConstraints); // Basic Constraints is usually marked as critical.
// -------------------------------------
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certBuilder.build(contentSigner));
}
}
CertificateUtils
using Amazon.KeyManagementService;
using Amazon.KeyManagementService.Model;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace iText.SigningExamples.AwsKms
{
public class CertificateUtils
{
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;
}
}
}
Note: The article assumes that you have stored your credentials in the default
section of your ~/.aws/credentials
file and your region in the default
section of your ~/.aws/config
file. Otherwise, you'll have to adapt the KmsClient
instantiation or initialization in the code examples written for this article.
For the other examples relating to this article, please see the following links: