Using iText and AWS KMS to digitally sign a PDF document: Part 3
This example was written for the article "Using iText and AWS KMS to digitally sign a PDF document" and shows an implementation of the iText IExternalSignature
interface for the purposes of signing a PDF using an AWS KMS Key Pair.
In the constructor we select a signing algorithm available for the key in question. However, as explained in the article you may want to enforce use of a specific hashing algorithm instead of simply taking the first algorithm.
In this example getHashAlgorithm
and getEncryptionAlgorithm
return the name of the respective part of the signature algorithm and sign
simply creates a signature.
AwsKmsSignature
package com.itextpdf.signingexamples.aws.kms;
import java.security.GeneralSecurityException;
import java.util.List;
import java.util.function.Function;
import com.itextpdf.signatures.IExternalSignature;
import com.itextpdf.signatures.ISignatureMechanismParams;
import com.itextpdf.signatures.RSASSAPSSMechanismParams;
import software.amazon.awssdk.core.SdkBytes;
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.MessageType;
import software.amazon.awssdk.services.kms.model.SignRequest;
import software.amazon.awssdk.services.kms.model.SignResponse;
import software.amazon.awssdk.services.kms.model.SigningAlgorithmSpec;
/**
* @author mkl
*/
public class AwsKmsSignature implements IExternalSignature {
public AwsKmsSignature(String keyId) {
this(keyId, a -> a != null && a.size() > 0 ? a.get(0) : null);
}
public AwsKmsSignature(String keyId, Function<List<SigningAlgorithmSpec>, SigningAlgorithmSpec> selector) {
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());
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:
case RSASSA_PSS_SHA_256:
case RSASSA_PSS_SHA_384:
case RSASSA_PSS_SHA_512:
break;
default:
throw new IllegalArgumentException(String.format("Unknown signing algorithm: %s", signingAlgorithmSpec));
}
}
}
@Override
public String getDigestAlgorithmName() {
switch(signingAlgorithmSpec) {
case ECDSA_SHA_256:
case RSASSA_PKCS1_V1_5_SHA_256:
case RSASSA_PSS_SHA_256:
return "SHA-256";
case ECDSA_SHA_384:
case RSASSA_PKCS1_V1_5_SHA_384:
case RSASSA_PSS_SHA_384:
return "SHA-384";
case ECDSA_SHA_512:
case RSASSA_PKCS1_V1_5_SHA_512:
case RSASSA_PSS_SHA_512:
return "SHA-512";
default:
return null;
}
}
@Override
public String getSignatureAlgorithmName() {
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";
case RSASSA_PSS_SHA_256:
case RSASSA_PSS_SHA_384:
case RSASSA_PSS_SHA_512:
return "RSASSA-PSS";
default:
return null;
}
}
@Override
public ISignatureMechanismParams getSignatureMechanismParameters() {
switch (signingAlgorithmSpec)
{
case RSASSA_PSS_SHA_256:
return RSASSAPSSMechanismParams.createForDigestAlgorithm("SHA-256");
case RSASSA_PSS_SHA_384:
return RSASSAPSSMechanismParams.createForDigestAlgorithm("SHA-384");
case RSASSA_PSS_SHA_512:
return RSASSAPSSMechanismParams.createForDigestAlgorithm("SHA-512");
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:
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
using Amazon.KeyManagementService;
using Amazon.KeyManagementService.Model;
using iText.Signatures;
using System;
using System.Collections.Generic;
using System.IO;
namespace iText.SigningExamples.AwsKms
{
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":
case "RSASSA_PSS_SHA_256":
case "RSASSA_PSS_SHA_384":
case "RSASSA_PSS_SHA_512":
break;
default:
throw new ArgumentException(String.Format("Unknown signing algorithm: {0}", signingAlgorithm));
}
}
}
public string GetSignatureAlgorithmName()
{
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";
case "RSASSA_PSS_SHA_256":
case "RSASSA_PSS_SHA_384":
case "RSASSA_PSS_SHA_512":
return "RSASSA-PSS";
default:
return null;
}
}
public string GetDigestAlgorithmName()
{
switch (signingAlgorithm)
{
case "ECDSA_SHA_256":
case "RSASSA_PKCS1_V1_5_SHA_256":
case "RSASSA_PSS_SHA_256":
return "SHA-256";
case "ECDSA_SHA_384":
case "RSASSA_PKCS1_V1_5_SHA_384":
case "RSASSA_PSS_SHA_384":
return "SHA-384";
case "ECDSA_SHA_512":
case "RSASSA_PKCS1_V1_5_SHA_512":
case "RSASSA_PSS_SHA_512":
return "SHA-512";
default:
return null;
}
}
public ISignatureMechanismParams GetSignatureMechanismParameters()
{
switch (signingAlgorithm)
{
case "RSASSA_PSS_SHA_256":
return RSASSAPSSMechanismParams.CreateForDigestAlgorithm("SHA-256");
case "RSASSA_PSS_SHA_384":
return RSASSAPSSMechanismParams.CreateForDigestAlgorithm("SHA-384");
case "RSASSA_PSS_SHA_512":
return RSASSAPSSMechanismParams.CreateForDigestAlgorithm("SHA-512");
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":
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;
}
}
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: