It came to me to rework old code which signs PDF files into new one, which signs MemoryStreams (byte arrays) that come and are sent by web services. Simple, right? Well, that was yesterday. Today I just can't get it to work.
This is the old code, which uses FileStreams and it works:
public static string OldPdfSigner(PdfReader pdfReader, string destination, string password, string reason, string location, string pathToPfx)
{
using (FileStream pfxFile = new FileStream(pathToPfx, FileMode.Open, FileAccess.Read))
{
...
using (PdfStamper st = PdfStamper.CreateSignature(pdfReader, new FileStream(destination, FileMode.Create, FileAccess.Write), '\0'))
{
PdfSignatureAppearance sap = st.SignatureAppearance;
sap.SetCrypto(key, chain, null, PdfSignatureAppearance.WINCER_SIGNED);
sap.Reason = reason;
sap.Location = location;
return destination;
}
}
}
Below is what I've redone myself which throws System.ObjectDisposedException: Cannot access a closed Stream.
public static byte[] PdfSigner(PdfReader pdfReader, string password, string reason, string location, string pathToPfx)
{
using (FileStream pfxFile = new FileStream(pathToPfx, FileMode.Open, FileAccess.Read))
{
...
MemoryStream outputStream = new MemoryStream();
using (PdfStamper st = PdfStamper.CreateSignature(pdfReader, outputStream, '\0'))
{
st.Writer.CloseStream = false;
PdfSignatureAppearance sap = st.SignatureAppearance;
sap.SetCrypto(key, chain, null, PdfSignatureAppearance.WINCER_SIGNED);
sap.Reason = reason;
sap.Location = location;
st.Close();
outputStream.Position = 0;
return outputStream.ToArray();
}
}
}
and if I comment out
st.Close();
it creates an empty document. What am I doing wrong?
Not specific to your signing code, but when working with MemoryStream and PdfStamper, follow this general pattern:
using (MemoryStream ms = new MemoryStream()) {
using (PdfStamper stamper = new PdfStamper(reader, ms, '\0', true)) {
// do stuff
}
return ms.ToArray();
}
MemoryStream implements IDisposable, so include a using statement.
The PdfStamper using statement takes care of disposing the object, so you don't need to call Close(), and don't need to set the CloseStream property.
Your code snippet is returning the byte array too soon, inside the PdfStamper using statement, so your MemoryStream is effectively a no-op. Return the byte array outside of the PdfStamper using statement, and inside the MemoryStream using statement.
Generally there's no need to reset the MemoryStream Position property.
Ignore the PdfStamper constructor above - it's from some test code I had for filling forms, and use whatever constructor/method you need to do your signing.
Related
Help please. I'm trying to get a "deferred" digital signature and embed it in a PDF document. On the server (.net core 3.1) I create a test pdf and add an empty container for the signature. To do this, I use the iTextSharp package (version 5.5.13.3):
public static byte[] GetPdfBytesToSign(byte[] unsignedPdf, string signatureFieldName)
{
//unsignedPdf - source PDF
using (PdfReader reader = new PdfReader(unsignedPdf))
{
//looking for existing signatures
int existingSignaturesNumber = reader.AcroFields.GetSignatureNames().Count;
using (MemoryStream tempPdf = new MemoryStream())
{
//add an empty container for a new signature
using (PdfStamper st = PdfStamper.CreateSignature(reader, tempPdf, '\0', null, true))
{
PdfSignatureAppearance appearance = st.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(205, 700, 390, 800), reader.NumberOfPages,
//set the name of the field, it will be needed later to insert a signature
$"{signatureFieldName}{existingSignaturesNumber + 1}");
ExternalBlankSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, BufferSize);
//get a stream that contains the sequence we want to sign
using (Stream contentStream = appearance.GetRangeStream())
{
MemoryStream memoryStream = new MemoryStream();
contentStream.CopyTo(memoryStream);
byte[] bytesToSign = memoryStream.ToArray();
return bytesToSign;
}
}
}
}
}
And here is the problem!!! For some reason, iText can't read the PDF it created with an empty signature container (to embed the received signature from the client).
public static byte[] GetSignedPdf(byte[] unsignedPdfWithContainer, byte[] signature)
{
using var pdfReader = new PdfReader(unsignedPdfWithContainer);//<--throwing an exception here
using var output = new MemoryStream();
var external = new MyExternalSignatureContainer(signature);
MakeSignature.SignDeferred(pdfReader, SignatureFieldName, output, external);
return output.ToArray();
}
I am getting the following exception:
iTextSharp.text.exceptions.InvalidPdfException: "Unexpected '>>' at file pointer 1432"
Test(source) pdf is created like this:
public static byte[] CreatePdf()
{
using (MemoryStream ms = new MemoryStream())
{
Document document = new Document(PageSize.A4, 25, 25, 30, 30);
PdfWriter writer = PdfWriter.GetInstance(document, ms);
document.Open();
document.Add(new Paragraph("Hello World!"));
document.Close();
writer.Close();
return ms.ToArray();
}
}
I'm attempting to write a method for encrypting existing pdf's and writing out the encrypted pdf to a memory stream, using the following code:
public byte[] ProtectPdfStreamWithPassword(
string filePath,
string password)
{
using (var outStream = new MemoryStream())
{
using (var reader = new PdfReader(filePath))
{
using (var stamper = new PdfStamper(reader, outStream))
{
var passwordBytes =
Encoding.ASCII.GetBytes(password);
stamper.SetEncryption(
passwordBytes,
passwordBytes,
PdfWriter.AllowPrinting,
PdfWriter.ENCRYPTION_AES_256);
return outStream.ToArray();
}
}
}
}
I'm following the same pattern I've seen used elsewhere online but I'm running into an issue where the MemoryStream being written into only ever has 15 bytes written to it, when the file passed to the PdfReader has around 8Kb. I did not run into this issue when working with FileStreams instead but I'd prefer to use MemoryStreams here, if possible. Any help would be appreciated.
Okay, so the issue for me was returning the MemoryStream bytes from within the PdfStamper using block. There must be an implicit Flush going on that wasn't happening because I was returning the bytes too soon. I refactored my code to the following, which works:
public byte[] ProtectPdfStreamWithPassword(
string filePath,
string password)
{
using (var outStream = new MemoryStream())
{
using (var reader = new PdfReader(filePath))
{
using (var stamper = new PdfStamper(reader, outStream))
{
var passwordBytes =
Encoding.ASCII.GetBytes(password);
stamper.SetEncryption(
passwordBytes,
passwordBytes,
PdfWriter.AllowPrinting,
PdfWriter.ENCRYPTION_AES_256);
}
}
return outStream.ToArray();
}
}
Here is my chunk of code. It compiles fine and when I fire off the event I get the email, but I then get this error
Email attachment ERROR on Adobe while opening(Acrobat could not open 'Att00002.pdf' because it is either not a supported file type or because the file has been damaged(for example, it was sent as an email attachment and wasnt correctly decoded.)
string agentName = "My Name";
MemoryStream _output = new MemoryStream();
PdfReader reader = new PdfReader("/pdf/Agent/Specialist_Certificate.pdf");
using (PdfStamper stamper = new PdfStamper(reader, _output))
{
AcroFields fields = stamper.AcroFields;
// set form fields
fields.SetField("FIELD_AGENT_NAME", agentName);
fields.SetField("FIELD_DATE", AvalonDate);
// flatten form fields and close document
stamper.FormFlattening = true;
SendEmail(_output);
DownloadAsPDF(_output);
stamper.Close();
}
private void SendEmail(MemoryStream ms)
{
Attachment attach = new Attachment(ms, new System.Net.Mime.ContentType("application/pdf"));
EmailHelper.SendEMail("myemail#myemail.com", "mjones#globusfamily.com", null, "", "Avalon Cert", "Hope this works", EmailHelper.EmailFormat.Html,attach);
}
EDITED *************************************
using (MemoryStream _output = new MemoryStream())
{
using (PdfStamper stamper = new PdfStamper(reader, _output))
{
AcroFields fields = stamper.AcroFields;
// set form fields
fields.SetField("FIELD_AGENT_NAME", agentName);
fields.SetField("FIELD_DATE", AvalonDate);
// flatten form fields and close document
stamper.FormFlattening = true;
}
SendEmail(_output);
}
You're calling stamper.close() inside the using (PdfStamper stamper = new PdfStamper(reader, _output)). The using will automatically close the stamper upon exiting it in addition to your manual close(), so technically the stamper is trying to be closed twice. Because of this it is also trying to close the MemoryStream more than once. That's where the exception is coming from.
I would use the technique located in the answer here for your MemoryStream and PdfStamper (modified and taken from: Getting PdfStamper to work with MemoryStreams (c#, itextsharp)):
using (MemoryStream _output = new MemoryStream()) {
using (PdfStamper stamper = new PdfStamper(reader, _output)) {
// do stuff
}
}
I am editing a pdf then print in a new tab. I am getting an error saying I "Cannot access a closed stream." This is what I have thus far...
CONTROLLER
public ActionResult QST(string ID)
{
...
PdfReader reader = new PdfReader(Server.MapPath("~/Content/PDF/QST.pdf"));
QstRepository repo = new QstRepository();
Stream newPdf = repo.GS(ID, reader);
return new FileStreamResult(newPdf, "application/pdf");
}
MODEL
public Stream GS(string ID, PdfReader reader)
{
Stream newPdf = new MemoryStream();
using (MemoryStream ms = new MemoryStream())
{
PdfStamper formFiller = new PdfStamper(reader, ms);
AcroFields formFields = formFiller.AcroFields;
formFields.SetField("ID", ID);
formFiller.FormFlattening = true;
formFiller.Writer.CloseStream = false;
newPdf = ms;
formFiller.Close();
}
return newPdf;
}
How do I access the pdf before it is closed... or how do I access the pdf after?
The last two lines of your code before you return are
newPdf = ms;
formFiller.Close();
Since your code is all within a using (MemoryStream ms = new MemoryStream()) block, the MemoryStream ms will be closed before you return anyway.
Since you are assigning newPdf to the variable ms, nwePdf eventualy returns the same (already closed) MemoryStream that ms points to.
You could have public Stream GS(string ID, PdfReader reader) just create the stream without the using block, but then you run the risk of it staying open if an exception occurs in your code. I suppose you could try the following:
// All calls to GS() must ensure that the returned stream gets closed.
public Stream GS(string ID, PdfReader reader)
{
MemoryStream newPdf = new MemoryStream();
PdfStamper formFiller = null;
try
{
formFiller = new PdfStamper(reader, newPdf);
AcroFields formFields = formFiller.AcroFields;
formFields.SetField("ID", ID);
formFiller.FormFlattening = true;
//formFiller.Writer.CloseStream = false;
}
catch
{
// Only close newPdf on an exception
newPdf.Close();
throw; // Rethrow original exception
}
finally
{
// Always ensure that formFiller gets closed
formFiller.Close();
}
return newPdf;
}
Update: I removed the extra MemoryStream, since the one was simply being declared at the beginning of the function, and then set to point to the other at the end, without ever being used for anything else in between.
Update: I found an example using the PdfStamper. According to the example, calling formFiller.Close() is what actually writes to the stream. In their example, they do not set the Writer.CloseStream property to false. That may be why you're getting a blank page. Unfortunately, I was not able to find any actual documentation on PdfStamper to verify this.
In your code, ms and newPdf refer to the same MemoryStream instance; since ms is closed at the end of the using block, newPdf is closed too (since it's the same stream). So you need to return the Stream without closing it:
public Stream GS(string ID, PdfReader reader)
{
PdfStamper formFiller = new PdfStamper(reader, ms);
AcroFields formFields = formFiller.AcroFields;
formFields.SetField("ID", ID);
formFiller.FormFlattening = true;
formFiller.Writer.CloseStream = false;
formFiller.Close();
return ms;
}
I'm not very familiar with ASP.NET MVC, but I assume that FileStreamResult takes care of closing the stream.
i have tried following the rules of code analysis on this method:
public static string Encrypt(string password)
{
string myPassword = string.Empty;
if (!string.IsNullOrEmpty(password))
{
myPassword = password;
byte[] Value = System.Text.Encoding.UTF8.GetBytes(myPassword);
SymmetricAlgorithm mCSP = new RijndaelManaged();
mCSP.Key = _key;
mCSP.IV = _initVector;
using (ICryptoTransform ct = mCSP.CreateEncryptor(mCSP.Key, mCSP.IV))
{
using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, ct, CryptoStreamMode.Write))
{
cs.Write(Value, 0, Value.Length);
cs.FlushFinalBlock();
cs.Close();
myPassword = Convert.ToBase64String(ms.ToArray());
}
}
}
}
return myPassword;
}
added all the Try {} Finaly{} blocks, but it was still yelling at me that i dont respect rule 2202.
anyone can give me a hand with this?
yes, i have read other posts about this subject and tried applying it,
but at the end i still get the same message.
To get rid of the CA2202 warning for cs, simply remove the call to its Close method.
The CA2202 problem for ms is a wee bit more complex. The warning is cropping up because CryptoStream has the effrontery to dispose the stream it received via is constructor, which means that there's one inappropriate call to ms.Close() that you can't avoid. The good news is that this untimely disposition has no side-effects in your case, and the same goes for the double disposition, so you can safely slap on a SuppressMessageAttribute and ignore the problem. (For cases where you actually need to passed stream to survive its unpreventable disposition by something like CryptoStream, the usual technique is to use a stream subclass whose disposition can be prevented by its instantiating code.)
Get rid of these two lines, they aren't needed:
cs.FlushFinalBlock();
cs.Close();
Following the documentation on this topic should lead to this code:
public static string Encrypt(string password)
{
string myPassword = string.Empty;
if (!string.IsNullOrEmpty(password))
{
myPassword = password;
byte[] Value = System.Text.Encoding.UTF8.GetBytes(myPassword);
SymmetricAlgorithm mCSP = new RijndaelManaged();
mCSP.Key = _key;
mCSP.IV = _initVector;
using (ICryptoTransform ct = mCSP.CreateEncryptor(mCSP.Key, mCSP.IV))
{
System.IO.MemoryStream ms = null;
try
{
ms = new System.IO.MemoryStream()
var tmp = ms;
using (CryptoStream cs = new CryptoStream(ms, ct,
CryptoStreamMode.Write))
{
ms = null;
cs.Write(Value, 0, Value.Length);
cs.FlushFinalBlock();
cs.Close();
myPassword = Convert.ToBase64String(tmp.ToArray());
}
}
finally
{
if(ms != null)
ms.Dispose();
}
}
}
return myPassword;
}
The documentation on this analysis warning (http://msdn.microsoft.com/en-us/library/ms182334.aspx) gives this example, similar to yours in that it's manipulating streams:
Stream stream = null;
try
{
stream = new FileStream("file.txt", FileMode.OpenOrCreate);
using (StreamWriter writer = new StreamWriter(stream))
{
stream = null;
// Use the writer object...
}
}
finally
{
if(stream != null)
stream.Dispose();
}
but this still gives the error. The following will solve the error:
Stream stream = null;
StreamWriter writer = null;
try
{
stream = new FileStream("file.txt", FileMode.OpenOrCreate);
writer = new StreamWriter(stream))
// Do some stuff on the stream writer..
}
finally
{
if(writer != null)
writer.Dispose();
else if(stream != null)
stream.Dispose();
}
The reason is simple; if the writer will always dispose the stream for you. Only in the scenario the writer is not successfully created should you dispose the stream yourself. But I must admit I like the following syntax a lot more, and if you create a MemoryStream instead of a FileStream the chance of an exception occurring is small and I would prefer suppressing the CA. Please notice that you can stack using statements, so a extra 'nesting level' is often not required.
using (Stream stream = new FileStream("file.txt", FileMode.OpenOrCreate))
using (StreamWriter writer = new StreamWriter(stream))
{
// Use the writer object...
}