Add Digital Signature to a PDF using IText 7 - itext

For IText 5, adding digital signature was fairly easy.
The link for its documentation is:
http://developers.itextpdf.com/examples/security/digital-signatures-white-paper/digital-signatures-chapter-2
Can someone share the link to documentation for doing so in ITEXT 7?
I have tried various ways to no avail. Could not find any links online. I can unsign and check signature, but can't add it.

Ports of the Digital Signatures Whitepaper code examples to iText 7 can be found in the iText 7 Java signature samples github repository test sources package com.itextpdf.samples.signatures, e.g. an excerpt from the simple C2_01_SignHelloWorld example:
public void sign(String src, String dest,
Certificate[] chain,
PrivateKey pk, String digestAlgorithm, String provider,
PdfSigner.CryptoStandard subfilter,
String reason, String location)
throws GeneralSecurityException, IOException {
// Creating the reader and the signer
PdfReader reader = new PdfReader(src);
PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), false);
// Creating the appearance
PdfSignatureAppearance appearance = signer.getSignatureAppearance()
.setReason(reason)
.setLocation(location)
.setReuseAppearance(false);
Rectangle rect = new Rectangle(36, 648, 200, 100);
appearance
.setPageRect(rect)
.setPageNumber(1);
signer.setFieldName("sig");
// Creating the signature
IExternalSignature pks = new PrivateKeySignature(pk, digestAlgorithm, provider);
IExternalDigest digest = new BouncyCastleDigest();
signer.signDetached(digest, pks, chain, null, null, null, 0, subfilter);
}

Related

Sign PDF with new Belgian id card (eid middleware and itext)

I try to sign some pdf's with a Belgian id card.
To acheive that, I'm using the belgium eid middleware to sign the data and itext7 to stamp the pdf with the signature.
I use a PdfSigner (itext) and I have implement a IExternalSignature to call the eid middleware to sign the message.
All work well for the Belgian id card 1.7 with encryption RSA and hash SHA256.
But when I try to sign with the new Belgian id card 1.8 with encryption ECDSA and hash SHA384, the signature can't be validated by adobe reader (or other reader).
"The document has been altered or corrupted".
It seems to be a mismatch somewhere in the hashing or ...
I search for some days but I have no more idea to fix that ...
Someone have an idea about what is going wrong?
Thanks in advance for your help.
Here some additional informations.
The external signature class:
internal sealed class BeIDSignature : IExternalSignature
{
public string GetEncryptionAlgorithm()
{
return eidWrapper.Instance.GetEncryptionAlgorithm().ToString();
}
public string GetHashAlgorithm()
{
switch (EidWrapper.Instance.GetEncryptionAlgorithm())
{
case EidWrapper.EncryptionAmgorithm.RSA:
return DigestAlgorithms.SHA256;
case EidWrapper.EncryptionAmgorithm.ECDSA:
return DigestAlgorithms.SHA384;
default:
return null;
}
}
public byte[] Sign(byte[] message)
{
return EidWrapper.Instance.SignData(message);
}
}
GetEncryptionAlgorithm will return RSA or ECDSA depending of the chip.
The sign method will use the eid-mw packege to generate the signature.
A little piece of code of the sign method of the EidWrapper:
if (key.KeyType.KeyType == CKK.EC)
{
session.SignInit(new Mechanism(CKM.ECDSA_SHA384), key);
return session.Sign(data);
}
else if (key.KeyType.KeyType == CKK.RSA)
{
session.SignInit(new Mechanism(CKM.SHA256_RSA_PKCS), key);
return session.Sign(data);
}
You can find here a zip with 3 pdf files:
The original file
One signed directly with adobe (siganture is ok)
One signed with eid-mw and itext (signature is NOT ok). But remember
that is working for RSA/SHA256 siganture.
https://easyupload.io/yzscsu
Thanks again for your time.
Here is a sample of an external container for the belgian eid smartcard.
The code is not fully implemented but you have a base to make a siganture in ECDSA/SHA384 correctly.
Hope that will help someone :)
internal sealed class BeIdExternalSignatureContainer : IExternalSignatureContainer
{
private IX509Store _crls = null;
private readonly IHttpClientFactory _httpClientFactory;
private int _crlsSize = 0;
public BeIdExternalSignatureContainer(IHttpClientFactory httpClientFactory)
{
this._httpClientFactory = httpClientFactory;
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
signDic.Put(PdfName.Filter, PdfName.Adobe_PPKLite);
signDic.Put(PdfName.SubFilter, PdfName.ETSI_CAdES_DETACHED);
}
public int ComputeEstimateSize()
{
// Base on itext estimation
if (this._crlsSize == 0)
{
this.InitializeCrls();
}
return (8192 + this._crlsSize + 4192) * 2 + 2;
}
public byte[] Sign(Stream data)
{
IReadOnlyDictionary<string, byte[]> certificatesBinaries = EidWrapper.Instance.GetCertificateFiles(new string[] {
Constants.SIGNATURE_CERTIFICATE_NAME,
Constants.CA_CERTIFICATE_NAME,
Constants.ROOT_CERTIFICATE_NAME
});
X509Certificate signCertificate = new(X509CertificateStructure.GetInstance(certificatesBinaries[Constants.SIGNATURE_CERTIFICATE_NAME]));
EidWrapper.EncryptionAlgorithms encryptionAlgorithm = EidWrapper.Instance.GetEncryptionAlgorithm();
SignerInfoGenerator signerGenerator = new SignerInfoGeneratorBuilder()
.WithSignedAttributeGenerator(new PadesSignedAttributeGenerator { SigningCertificate = signCertificate })
.WithUnsignedAttributeGenerator(new PadesUnsignedAttributeGenerator(this._httpClientFactory))
.Build(new BeIdSignatureFactory(encryptionAlgorithm), signCertificate);
CmsSignedDataGenerator gen = new();
gen.AddSignerInfoGenerator(signerGenerator);
IX509Store x509CertStore = X509StoreFactory.Create(
"Certificate/Collection",
new X509CollectionStoreParameters(certificatesBinaries.Values.Select(b => new X509Certificate(X509CertificateStructure.GetInstance(b))).ToList()));
gen.AddCertificates(x509CertStore);
gen.AddCrls(this.Crls);
CmsProcessableInputStream cmsProcessableInputStream = new(data);
CmsSignedData signedData = gen.Generate(cmsProcessableInputStream, false);
return signedData.GetEncoded();
}
private IX509Store Crls
{
get
{
if (this._crls == null)
{
this.InitializeCrls();
}
return this._crls;
}
}
private void InitializeCrls()
{
this._crlsSize = 0;
List<X509Crl> crls = new();
X509CrlParser crlParser = new();
HttpClient httpClient = this._httpClientFactory.CreateClient();
foreach (var crlUrl in BeIdSignatureConstants.CRL_URL_COLLECTION)
{
using HttpResponseMessage response = httpClient.Send(new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = new Uri(crlUrl) });
if (response.IsSuccessStatusCode)
{
using MemoryStream ms = new MemoryStream();
response.Content.ReadAsStream().CopyTo(ms);
byte[] crlBytes = ms.ToArray();
this._crlsSize += crlBytes.Length;
crls.Add(crlParser.ReadCrl(crlBytes));
}
}
this._crls = X509StoreFactory.Create(
"CRL/Collection",
new X509CollectionStoreParameters(crls));
}
}
internal class BeIdSignatureFactory : ISignatureFactory
{
private readonly EidWrapper.EncryptionAlgorithms _encryptionAlgorithm;
public BeIdSignatureFactory(EidWrapper.EncryptionAlgorithms encryptionAlgorithm)
{
this._encryptionAlgorithm = encryptionAlgorithm;
}
public IStreamCalculator CreateCalculator()
{
BeIdSigner signer = new(this._encryptionAlgorithm);
signer.Init(true, null);
return new DefaultSignatureCalculator(signer);
}
public object AlgorithmDetails
{
get
{
return MapAlgorithm(this._encryptionAlgorithm);
}
}
public static AlgorithmIdentifier MapAlgorithm(EidWrapper.EncryptionAlgorithms encryptionAlgorithm)
{
switch (encryptionAlgorithm)
{
case EidWrapper.EncryptionAlgorithms.RSA:
return new AlgorithmIdentifier(PkcsObjectIdentifiers.Sha256WithRsaEncryption, DerNull.Instance);
case EidWrapper.EncryptionAlgorithms.ECDSA:
return new AlgorithmIdentifier(X9ObjectIdentifiers.ECDsaWithSha384, DerNull.Instance);
default:
throw new ArgumentException($"Unsupported encryption algorithm: {encryptionAlgorithm}");
}
}
}
internal class BeIdSigner : ISigner
{
private byte[] _input;
private readonly EidWrapper.EncryptionAlgorithms _encryptionAlgorithm;
public BeIdSigner(EidWrapper.EncryptionAlgorithms encryptionAlgorithm)
{
this._encryptionAlgorithm = encryptionAlgorithm;
}
public void Init(bool forSigning, ICipherParameters parameters)
{
this.Reset();
}
public void Update(byte input)
{
throw new NotImplementedException("The \"ISigner.Update\" method is not implemented for the \"BeIdSigner\" class.");
}
public void BlockUpdate(byte[] input, int inOff, int length)
{
this._input = input.Skip(inOff).Take(length).ToArray();
}
public byte[] GenerateSignature()
{
return this._encryptionAlgorithm == EidWrapper.EncryptionAlgorithms.ECDSA
? PlainToDer(EidWrapper.Instance.SignData(this._input))
: EidWrapper.Instance.SignData(this._input);
}
public bool VerifySignature(byte[] signature)
{
throw new NotImplementedException("The \"ISigner.VerifySignature\" method is not implemented for the \"BeIdSigner\" class.");
}
public void Reset()
{
this._input = null;
}
public string AlgorithmName => throw new NotImplementedException("The \"ISigner.AlgorithmName\" property is not implemented for the \"BeIdSigner\" class.");
private static byte[] PlainToDer(byte[] plain)
{
int valueLength = plain.Length / 2;
BigInteger r = new(1, plain, 0, valueLength);
BigInteger s = new(1, plain, valueLength, valueLength);
return new DerSequence(new DerInteger(r), new DerInteger(s)).GetEncoded(Asn1Encodable.Der);
}
}
This answer sums up the comments to the question.
iText and ECDSA signatures
First of all one has to realize that currently (i.e. for iText 7.2.2) ECDSA is not supported by all parts of the iText signing API.
This limitation is mostly due to the iText PdfPKCS7 class used to create and validate CMS signature containers embedded in PDFs. When it is used to create a signature container, the identifier it stores in the signatureAlgorithm field does not take the used hash algorithm into account. E.g. it uses the same value for RSA (with PKCS1 v1.5 padding) signatures, no matter if it's actually SHA1withRSA or SHA256withRSA or whichever combination.
For RSA this is ok because there indeed is an identifier that can be used for all these cases. For DSA it is somewhat ok because in many contexts DSA is limited to use with SHA1 only.
For ECDSA this is not ok, there only are identifiers taking the hash algorithm into account. iText uses the EC public key identifier in all these cases which is simply wrong. The reason why hardly anyone noticed this bug is that Adobe Acrobat validation apparently ignores the contents of this signatureAlgorithm field: You can even write the RSA identifier into this field of an ECDSA signature and the validation succeeds without an indication of a problem.
To create proper ECDSA signatures, therefore, one currently should not use the PdfPKCS7 class. As all the PdfSigner.signDetached methods internally use the PdfPKCS7 class, this in turn means that one must not use them but instead PdfSigner.signExternalContainer. As a consequence, one must not use an IExternalSignature implementation to retrieve one's signature value but instead an IExternalSignatureContainer implementation in which one builds the CMS signature container differently, for example using BouncyCastle classes.
In the case at hand the BeIDSignature implementation of IExternalSignature, therefore, must be replaced accordingly.
For further details please read the section Which Interface to Use of the iText knowledge base article Digital Signing with iText 7.
ECDSA signature formats
There are two major formats in which an ECDSA signature value can be stored, either as a TLV (DER) encoded sequence of two integers or (plain encoding) as the concatenation of fixed length representations of those two integers.
Depending on the used format one has to use specific algorithm identifiers for ECDSA and PLAIN-ECDSA respectively. If one needs a specific identifier, one can convert the signature value from one format to the other.
In the case at hand the Belgian ID card returns the ECDSA signature value in plain format. To use the more common non-PLAIN ECDSA identifiers, one has to convert that value to the DER format. This can be done using this method:
byte[] PlainToDer(byte[] plain)
{
int valueLength = plain.Length / 2;
BigInteger r = new BigInteger(1, plain, 0, valueLength);
BigInteger s = new BigInteger(1, plain, valueLength, valueLength);
return new DerSequence(new DerInteger(r), new DerInteger(s)).GetEncoded(Asn1Encodable.Der);
}
(X509Certificate2Signature helper method from the code examples of Digital Signing with iText 7)
Some thoughts:
Are you sure that there is an ECDSA key available on the chip ? I had a short look at the documentation (not sure it's up to date - cf. eid-mw github), which only mentions RSA. Additionally if you can sign using RSA/SHA256, having ECDSA support as well would mean that there's a second key pair on the card - I have some doubts about this ;
Try to sign with ECDSA / SHA384 in Adobe Reader using your eID - check whether you can validate the signature ;
Validate the signature online, using the SD-DSS tool: the diagnostic data may help you in pin-pointing what is wrong (e.g. a sha384 digest was generated, but the signature structure mentions sha256 as digest algo).

itext7: deferred signing not producing a valid signed pdf

I'm trying to use itext7 to sign a pdf by getting the signature from an external entity. I must be missing something because the deferred pdf signing is not valid. Lets start with the code for deferred signing:
public byte[] GetDocHashFromPreparedDocToSign(string pathToOriginalPdf, string pathToPreparedToBeSignedPdf, List<X509Certificate> certificates) {
var pdfSigner = new PdfSigner(new PdfReader(pathToOriginalPdf),
new FileStream(pathToPreparedToBeSignedPdf, FileMode.Create),
new StampingProperties());
pdfSigner.SetFieldName(_signatureFieldname);
var appearance = pdfSigner.GetSignatureAppearance();
appearance.SetPageRect(new Rectangle(144, 144, 200, 100))
.SetPageNumber(1)
.SetCertificate(certificates[0]);
var container = new ExternalBlankSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
pdfSigner.SignExternalContainer(container, 8192);
byte[] sha256SigPrefix = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09,
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
0x05, 0x00, 0x04, 0x20 };
// service needs to receive sha256 prepended
using var stream = File.OpenRead(pathToPreparedToBeSignedPdf);
var data = DigestAlgorithms.Digest(stream, DigestAlgorithms.SHA256);
var totalHash = new byte[sha256SigPrefix.Length + data.Length];
sha256SigPrefix.CopyTo(totalHash, 0);
data.CopyTo(totalHash, sha256SigPrefix.Length);
return totalHash;
}
The method received the path to the original pdf, the path to the temporary pdf that will contain the placeholder for the signature and a list of X509Certificate that is retrieved from the original service. After reserving the space for the signature, the method calculates the file's hash and prepends it with the sha256 prefix (required by the service that will sign the document).
This information is sent to the service which will return the signature. When the signature is retrieved, the following method is called for filling the signature placeholder with the real signature:
public void SignPreparedToBeSignedDoc(string pathToPreparedToBeSignedPdf, string pathToSignedFile, byte[] signature) {
var document = new PdfDocument(new PdfReader(pathToPreparedToBeSignedPdf));
using var writer = new FileStream(pathToSignedFile, FileMode.Create);
var container = new ExternalInjectingSignatureContainer(signature);
PdfSigner.SignDeferred(document, _signatureFieldname, writer, container);
}
EDIT: based on #mkl comment, I've fixed the signing part:
And here's the ExternalInjectingSignatureContainer:
internal class ExternalInjectingSignatureContainer : IExternalSignatureContainer {
private readonly byte[] _signature;
public ExternalInjectingSignatureContainer(byte[] signature) {
_signature = signature;
}
public byte[] Sign(Stream data){
var sgn = new PdfPKCS7(null, _certificates.ToArray(), "SHA256", false);
sgn.SetExternalDigest(_signature, null, "RSA");
return sgn.GetEncodedPKCS7();
}
public void ModifySigningDictionary(PdfDictionary signDic) {
}
}
Even though the code runs without errors, opening the pdf in adobe shows the following error:
EDIT: after fixing the signing code, now the error is different: it will show the signature info but it will say that the file has been changed or is corrupted.
At this point, it seems like the temporary pdf is being generated correctly, but I'm probably missing something...any clues on how I might debug this issue?
Thanks
EDIT: in response to the comments to the solution presented by #mkl, I've tried to update the code. I had a couple more minutes to play with this today and I've tried to follow the presented guidelines, but I'm clearly still missing something.
Before showing the new code, I'd just like to point out that the previous updated version (that whas using 2 IExternalSignatureContainer instances) seemed to be working correctly. ie, opening the signed pdf on adobe would only show me the yellow warning saying that there was something wrong with the signature:
Since the doc is being used with a test chain, it seems like the signing worked ok (though I might be completely wrong).
So, in order to fix the incorrect usage of the container, I've rewritten the code for the IExternalSignatureContainer's Sign methods. Here's the code I've got for the one that prepares the document hash that is going to be sent to the server:
public override byte[] Sign(Stream data) {
// create PCKS7 for getting attributes
var sgn = new PdfPKCS7(null, _certificates.ToArray(), DigestAlgorithms.SHA256, false);
// get document hash
DocumentDigest = DigestAlgorithms.Digest(data, DigestAlgorithms.SHA256);
// get attributes
var docBytesHash = sgn.GetAuthenticatedAttributeBytes(DocumentDigest,
PdfSigner.CryptoStandard.CMS,
null,
null);
//prepend sha256 prefix
var totalHash = new byte[_sha256SigPrefix.Length + docBytesHash.Length];
_sha256SigPrefix.CopyTo(totalHash, 0);
docBytesHash.CopyTo(totalHash, _sha256SigPrefix.Length);
DataToSend = totalHash;
return new byte[0];
}
Since I must call the GetEncodedPKCS7 method with the same parameters that were passed to GetAuthenticatedAttributes, I'm also saving the document hash obtained through the Digest method. DataToSend will be sent to the server so that it can return the signature for that hash.
And here's the code for the other IExternalSignatureContainer that will be called for the deferred signing (PdfSigner.SignDeferred):
public byte[] Sign(Stream data) {
// create CMS
var sgn = new PdfPKCS7(null, _certificates.ToArray(), DigestAlgorithms.SHA256, false);
// set the signature bytes
sgn.SetExternalDigest(_signature, null, "RSA");
// call GetEncoded with the same parameters as the original GetAuthenticatedAtt...
//_documentHash == DocumentDigest previous sample
var encodedSig = sgn.GetEncodedPKCS7(_documentHash,
PdfSigner.CryptoStandard.CMS,
null,
null,
null);
return encodedSig;
}
Unfortunately, I must be missing something (or lots of things):
Did I completely missed your point?
EDIT: Once again, following #mkl's lead, I was able to make it work. Like he said, you need to hash the GetAuthenticatedAttributeBytes value:
public override byte[] Sign(Stream data) {
// create PCKS7 for getting attributes
var sgn = new PdfPKCS7(null, _certificates.ToArray(), DigestAlgorithms.SHA256, false);
// get document hash
DocumentDigest = DigestAlgorithms.Digest(data, DigestAlgorithms.SHA256);
// get attributes
var docBytes = sgn.GetAuthenticatedAttributeBytes(DocumentDigest,
PdfSigner.CryptoStandard.CMS,
null,
null);
// hash dochBytes
using var hashMemoryStream = new MemoryStream(docBytes, false);
var docBytesHash = DigestAlgorithms.Digest(hashMemoryStream,
DigestAlgorithms.SHA256);
//prepend sha256 prefix
var totalHash = new byte[_sha256SigPrefix.Length + docBytesHash.Length];
_sha256SigPrefix.CopyTo(totalHash, 0);
docBytesHash.CopyTo(totalHash, _sha256SigPrefix.Length);
DataToSend = totalHash;
return new byte[0];
}
Thanks again.
There are two apparent issues, you hash the wrong data and you inject a wrong type of signature:
Hashing the wrong data
You calculate the hash to be signed like this:
using var stream = File.OpenRead(pathToPreparedToBeSignedPdf);
var data = DigestAlgorithms.Digest(stream, DigestAlgorithms.SHA256);
This is not correct.
A signed PDF essentially has this structure (read here for more details):
(By the way, the sketch is not 100% correct as the angled bracket delimiters '<' and '>' around the signature value must also not be hashed.)
Your prepared PDF at pathToPreparedToBeSignedPdf has the same structure, merely the "signature value" is not yet an actual signature value but instead a placeholder of 8192 hex-encoded zero bytes (8192 because that's the number you gave in pdfSigner.SignExternalContainer).
As you can see in the sketch, though, the signature value (or in your case, the placeholder) must not be hashed for signing.
The easiest way to retrieve the prepared PDF except the placeholder is inside the IExternalSignatureContainer implementation you use for preparing the PDF as its Sign method as parameter gets a stream containing exactly that. So instead of the ExternalBlankSignatureContainer use something like the ExternalEmptySignatureContainer from this answer:
public class ExternalEmptySignatureContainer : IExternalSignatureContainer
{
public void ModifySigningDictionary(PdfDictionary signDic)
{
signDic.Put(PdfName.Filter, PdfName.Adobe_PPKLite);
signDic.Put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached);
}
public byte[] Sign(Stream data)
{
// Store the data to sign and return an empty array
Data = DigestAlgorithms.Digest(data, DigestAlgorithms.SHA256);
return new byte[0];
}
public byte[] Data;
}
and after preparing retrieve the byte[] from its Data member:
var container = new ExternalEmptySignatureContainer();
pdfSigner.SignExternalContainer(container, 8192);
byte[] hash = container.Data;
In your case you may have to prepend the sha256SigPrefix to get a complete encoded DigestInfo object.
Injecting a wrong type of signature
Furthermore, considering your screen shot
you apparently inject a signature of a wrong type. You set a subfilter PdfName.Adbe_pkcs7_detached which implies that the signature to embed is a CMS signature container with a single SignerInfo signing the signed bytes of the PDF. The error message indicates, though, that the signature container you embed either is broken or is not a CMS signature container to start with.
After your edit: Injecting a signature container with incorrect contents
To fix the previous issue, "Injecting a wrong type of signature", you changed your ExternalInjectingSignatureContainer to build a CMS signature container for your signature bytes like this:
var sgn = new PdfPKCS7(null, _certificates.ToArray(), "SHA256", false);
sgn.SetExternalDigest(_signature, null, "RSA");
return sgn.GetEncodedPKCS7();
This, unfortunately, uses the PdfPKCS7 class incorrectly resulting in a CMS signature container with incorrect data.
(Correction: The resulting CMS container is not incorrect per se, but it is structurally extremely simply. Nowadays many profiles - e.g. European PAdES - require additional, signed attributes, and to satisfy such profiles you have to fix as described in the following text. If you only need Adobe Reader to show valid, though, the code above suffices for you.)
To fix you can either indeed build a CMS signature container yourself, using the iText PdfPKCS7 class or other means (like BouncyCastle or built-in .Net classes), or you can let iText do that for you.
The easiest approach is the latter one. You actually don't need deferred signing here at all, you simply implement IExternalSignature so that it calls your remote service:
public class RemoteSignature : IExternalSignature
{
public virtual byte[] Sign(byte[] message) {
IDigest messageDigest = DigestUtilities.GetDigest(GetHashAlgorithm());
byte[] messageHash = DigestAlgorithms.Digest(messageDigest, message);
byte[] digestInfo = [... prefix messageHash with sha256SigPrefix ...];
//
// Request signature for DigestInfo digestInfo
// and return signature bytes
//
return CALL_YOUR_SERVICE_FOR_SIGNATURE_OF_HASH(digestInfo);
}
public virtual String GetHashAlgorithm() {
return "SHA-256";
}
public virtual String GetEncryptionAlgorithm() {
return "RSA";
}
}
Now you can simply sign doing
var pdfSigner = new PdfSigner(new PdfReader(pathToOriginalPdf),
new FileStream(pathToSignedFile, FileMode.Create),
new StampingProperties());
pdfSigner.SetFieldName(_signatureFieldname);
var appearance = pdfSigner.GetSignatureAppearance();
appearance.SetPageRect(new Rectangle(144, 144, 200, 100))
.SetPageNumber(1)
.SetCertificate(certificates[0]);
var signature = new RemoteSignature();
pdfSigner.SignDetached(signature, certificates, null, null, null, 0, PdfSigner.CryptoStandard.CMS);

Spring RestTemplate POST file with UTF-8 filename

I'm using Spring RestTemplate to perform POST request sending a PDF file. The filename contains some UTF-8 characters (e.g. é, è, à, ê, ë).
The problem is that after sending the request, on the other side where the request is received, the filename doesn't have the expected UTF-8 characters, and I have something like ?popi?.pdf instead.
I've tried to explicitly set UTF-8 charset in RestTemplate, but it still doesn't work.
Here is my code,
public SomeThing storeFile(InputStream content, String fileName) {
Charset utf8 = Charset.forName("UTF-8");
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headersFile = new HttpHeaders();
headersFile.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headersFile.setContentDispositionFormData("file", fileName);
List<Charset> listCharSet = new ArrayList<Charset>();
listCharSet.add(utf8);
headersFile.setAcceptCharset(listCharSet);
InputStreamResource inputStreamResource = new InputStreamResource(content);
HttpEntity<InputStreamResource> requestEntityFile = new HttpEntity<>(inputStreamResource, headersFile);
MultiValueMap<String, Object> multipartRequest = new LinkedMultiValueMap<>();
multipartRequest.add("file", requestEntityFile);
RestTemplate newRestTemplate = new RestTemplate();
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
HttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
newRestTemplate.getMessageConverters().add(0, stringHttpMessageConverter);
newRestTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
FormHttpMessageConverter convForm = new FormHttpMessageConverter();
convForm.setCharset(utf8);
newRestTemplate.getMessageConverters().add(convForm);
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(multipartRequest, header);
ResponseEntity<String> result = newRestTemplate.postForEntity(env.getProperty("core.endpoint") + "/documents", requestEntity, String.class);
}
according to rfc7578 when you POST a file with multipart/form-data you should use "percent-encoding" instead of filename*
NOTE: The encoding method described in [RFC5987], which would add a
"filename*" parameter to the Content-Disposition header field, MUST NOT be used.
it could be easyly realesed:
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(0, new FormHttpMessageConverter() {
#Override
protected String getFilename(Object part) {
if (part instanceof Resource) {
Resource resource = (Resource) part;
try {
return URLEncoder.encode(resource.getFilename(), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
} else {
return null;
}
}
});
Most likely the file encoding's system property of the used JVM hasn't been explicitly set, meanwhile the operating system where JVM runs is not using UTF-8 as the default charset. For instance, if JVM runs on Windows, and we don't specify the charset, the default value will be Windows-1252 instead.
Could you double check the JVM arguments of both applications that send and receive the file? Please ensure that it has -Dfile.encoding=UTF-8 argument before specifying the main class name.
Please also ensure that the application/service which receives the file has been configured to accept UTF-8 charset.
Feel free to also check the other possible related answers, if adding the file.encoding argument on JVMs doesn't help solving the problem,
How to get UTF-8 working in Java webapps?
Spring MVC UTF-8
Encoding

Call a servlet using post request with large amount of data and open url in new tab [duplicate]

This question already has answers here:
Is it possible to download a file from server to my device having MGWT&GwtPhoneGap app?
(2 answers)
Closed 8 years ago.
I am using gwt canvas .
I am having 74kb string(image) data which I want to pass it to servlet. So that servlet process that data and throw the contents to the browser . In this way it will prompt user to download it.
From client side I am using RequestBuilder to call a servlet ,set request data to it ,data is large so I am using post request. It is hitting servlet also throwing contents on the browser but not showing anything downloading.
The current url having canvas .I thinks that's why it is not downloading anything this conclusion is because If am opening that servlet directly using http://localhost:8080/servlet then its downloading it property (In this case i am not proving any content from client side) but for the url having canvas its giving problem.
So is there any way where i can open a url in new tab and can call servlet using post request in gwt.
You can use a Form Panel for do the upload, the Form Panel will use a hidden iframe,
FormPanel form = new FormPanel();
form.setMethod(FormPanel.METHOD_POST);
form.setAction("/downloadServlet");
FlowPanel hiddenPanel = new FlowPanel();
hiddenPanel.add(new Hidden("name1", "value"));
hiddenPanel.add(new Hidden("name2", "value"));
form.setWidget(hiddenPanel);
RootPanel.get().add(form);
form.submit();
The content return by the servlet if you put the correct header will be downloaded by the user navigator.
public class ServletDownloadDemo extends HttpServlet{
private static final int BYTES_DOWNLOAD = 1024;
#Override
public void doPost(HttpServletRequest request,
HttpServletResponse response) throws IOException{
//Get Parameters
String name1 = request.getParameter("name1");
String name2 = request.getParameter("name2");
response.setContentType("text/plain");
response.setHeader("Content-Disposition",
"attachment;filename=downloadname.txt");
ServletContext ctx = getServletContext();
InputStream is = ctx.getResourceAsStream("/testing.txt");
int read=0;
byte[] bytes = new byte[BYTES_DOWNLOAD];
OutputStream os = response.getOutputStream();
while((read = is.read(bytes))!= -1){
os.write(bytes, 0, read);
}
os.flush();
os.close();
}
}

Bouncycastle: X509CertificateHolder to X509Certificate?

In versions prior to r146 it was possible to create X509Certificate objects directly.
Now that API is deprecated and the new one only deliveres a X509CertificateHolder object.
I cannot find a way to transform a X509CertificateHolder to X509Certificate.
How can this be done?
I will answer to my own questions, but not delete it, in case someone else got the same problems:
return new JcaX509CertificateConverter().getCertificate(certificateHolder);
And for attribute certificates:
return new X509V2AttributeCertificate(attributeCertificateHolder.getEncoded());
Not nice, as it is encoding and decoding, but it works.
Another option is this one :)
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(certificateHolder.getEncoded());
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);
This is an possibility to get the X509CertificateHolder to X509Certificate and toString. (first sentence of the code is irrelevant)
X509CertificateHolder selfSignedCertificate = CertificateUtils.selfSignCertificate(certificationRequest, keyPair.getPrivate());
byte[] content = selfSignedCertificate.getEncoded();
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(content));
logger.debug("cert: {}", cert.toString());
........