iText 7: Certificate-encrypted PDF returns no permissions - itext

When I use iText 7 for .NET to read a PDF file encrypted using a certificate and inspect the permissions, the GetPermissions() method returns 0.
Package version: 7.2.1
Operating system: Windows 10
Here is the code that I use to encrypt an existing PDF file:
using var inputPdfReader = PdfReaderFactory.CreatePdfReader("plain.pdf");
using var outputStream = new FileStream("encrypted.pdf", FileMode.Create);
var certificate = new X509Certificate2("mycert.pfx", "secret", X509KeyStorageFlags.Exportable);
var writerProperties = new WriterProperties()
.SetPublicKeyEncryption(
new[] { DotNetUtilities.FromX509Certificate(certificate) },
new[] { EncryptionConstants.ALLOW_COPY | EncryptionConstants.ALLOW_DEGRADED_PRINTING },
Encryption);
using var pdfWriter = new PdfWriter(outputStream, writerProperties);
using var outputPdfDocument = new PdfDocument(inputPdfReader, pdfWriter);
As long as the certificate is installed in my certificate store I can open the PDF using Acrobat Reader, and it displays the expected permissions:
However, when I read this PDF using iText 7, the permissions come back as 0:
var certificate = new X509Certificate2("mycert.pfx", "secret", X509KeyStorageFlags.Exportable);
if (!certificate.HasPrivateKey)
{
throw new NotSupportedException("Certificate must have a private key.");
}
var bouncyCertificate = DotNetUtilities.FromX509Certificate(certificate);
var keyPair = DotNetUtilities.GetKeyPair(certificate.GetRSAPrivateKey());
var readerProperties = new ReaderProperties()
.SetPublicKeySecurityParams(bouncyCertificate, keyPair.Private);
using var pdfReader = new PdfReader("encrypted.pdf", readerProperties);
using var pdfDocument = new PdfDocument(pdfReader);
int permissions = (int)pdfReader.GetPermissions();
// permissions == 0
This looks like a bug in iText 7. Am I missing something?

Related

Create and download Word file in Blazor

I am trying to create a Word file and download the created file in clients browser.
The creation part seems to work fine and I can open the file manually from its Folder.
But the downloaded file in browser does not open correctly and produces an error
"The file is corrupt and cannot be opened"
I am using the code from here
Microsoft instructions for downloading a file in Blazor
My code seems like this
private async Task CreateAndDownloadWordFile()
{
var destination = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var fileName = destination + "\\test12.docx";
//SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(destination, SpreadsheetDocumentType.Workbook);
using (WordprocessingDocument doc = WordprocessingDocument.Create
(fileName, DocumentFormat.OpenXml.WordprocessingDocumentType.Document))
{
// Add a main document part.
MainDocumentPart mainPart = doc.AddMainDocumentPart();
// Create the document structure and add some text.
mainPart.Document = new Document();
Body body = mainPart.Document.AppendChild(new Body());
Paragraph para = body.AppendChild(new Paragraph());
Run run = para.AppendChild(new Run());
// String msg contains the text, "Hello, Word!"
run.AppendChild(new Text("New text in document"));
}
var fileStream = GetFileStream();
using var streamRef = new DotNetStreamReference(stream: fileStream);
await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
}
private Stream GetFileStream()
{
var randomBinaryData = new byte[50 * 1024];
var fileStream = new MemoryStream(randomBinaryData);
return fileStream;
}
And I use this Javascript code
async function downloadFileFromStream(fileName, contentStreamReference) {
const arrayBuffer = await contentStreamReference.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
triggerFileDownload(fileName, url);
URL.revokeObjectURL(url);
}
function triggerFileDownload(fileName, url) {
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download = fileName ?? '';
anchorElement.click();
anchorElement.remove();
}
Any ideas?
But the downloaded file in browser does not open correctly
That is probably because you
First create a Word document
And then download var randomBinaryData = new byte[50 * 1024];
the downloaded file in browser
Check those. Are they exactly 50 * 1024 bytes ?
--
Also, you shouldn't pass the full C:\... path to the download funtion.
var fileStream = File.OpenRead(filename);
using var streamRef = new DotNetStreamReference(stream: fileStream);
//await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
await JS.InvokeVoidAsync("downloadFileFromStream", "suggestedName", streamRef);

Using Temp File Method for signing in iText Java instead of depending on System Memory

We are using PKCS7 Signatures and are signing a document on the server using a signature created on the client. We are creating Pdf Objects in memory, and want to switch to a system where we do not need to depend on system memory. Currently, I am saving the PdfSignatureAppearance Object created while generating hash in session,and then using this object from session when I receive a response from client (Signed Hash Content). Could you please help me figure out a way through which I do not need to save the PdfSignatureAppearance object in session and can directly use the Temp File for signing?
First, we generate a hash of file after inserting signature appearance as shown in the code below:
File file = new File("To be signed file location")
char version = '\u0000'
ByteArrayOutputStream out = new ByteArrayOutputStream()
String outputFile = "Signed file name"
FileOutputStream fileOutputStream = new FileOutputStream(outputFile)
PdfReader pdfReader = new PdfReader(new FileInputStream(file.absolutePath))
PdfStamper stamper = new PdfStamper(pdfReader, out, version, true)
PdfFormField pdfFormField = PdfFormField.createSignature(stamper.getWriter())
String signatureName = "Signature1"
pdfFormField.setWidget(new Rectangle((float) 20, (float) 20, (float) 100, (float) 60), (PdfName) null)
pdfFormField.setFlags(4)
pdfFormField.put(PdfName.DA, new PdfString("/Helv 0 Tf 0 g"))
pdfFormField.setFieldName(signatureName)
pdfFormField.setPage(1)
stamper.addAnnotation(pdfFormField, i)
stamper.close()
pdfReader = new PdfReader(out.toByteArray())
stamper = PdfStamper.createSignature(pdfReader, fileOutputStream, version, (File) null, true)
PdfSignatureAppearance appearance = stamper.getSignatureAppearance()
appearance.setLayer2Text("Digitally Signed by Name")
appearance.setImage(Image.getInstance(esignRequestCO.logoLocation))
appearance.setAcro6Layers(true)
Calendar cal = Calendar.getInstance()
cal.setTime(new Date())
cal.add(12, 5)
appearance.setSignDate(cal)
appearance.setVisibleSignature(signatureName)
int contentEstimated = 16384
HashMap<PdfName, Integer> exc = new HashMap()
exc.put(PdfName.CONTENTS, contentEstimated * 2 + 2)
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED)
dic.setReason(appearance.getReason())
dic.setLocation(appearance.getLocation())
dic.setDate(new PdfDate(appearance.getSignDate()))
appearance.setCryptoDictionary(dic)
appearance.preClose(exc)
InputStream inp = appearance.getRangeStream()
byte[] bytes = IOUtils.toByteArray(inp)
String hash = DigestUtils.sha256Hex(bytes)
As soon as we get the signed content, we insert the content in the file using same appearance object as shown below:
int contentEstimated = 16384
PdfSignatureAppearance appearance = "PdfSignatureAppearance object from session generated in previous step"
byte[] p7barray = "signed content here".bytes
byte[] paddedSig = new byte[contentEstimated]
System.arraycopy(p7barray, 0, paddedSig, 0, p7barray.length)
PdfDictionary dic2 = new PdfDictionary()
dic2.put(PdfName.CONTENTS, (new PdfString(paddedSig)).setHexWriting(true))
appearance.close(dic2)

Is it possible to merge several pdfs using iText7

I have several datasheets for products. Each is a separate file. What I want to do is to use iText to generate a summary / recommended set of actions, based on answers to a webform, and then append to that all the relevant datasheets. This way, I only need to open one new tab in the browser to print all information, rather than opening one for the summary, and one for each datasheet that is needed.
So, is it possible to do this using iText?
Yes, you can merge PDFs using iText 7. E.g. look at the iText 7 Jump-Start tutorial sample C06E04_88th_Oscar_Combine, the pivotal code is:
PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
PdfMerger merger = new PdfMerger(pdf);
//Add pages from the first document
PdfDocument firstSourcePdf = new PdfDocument(new PdfReader(SRC1));
merger.merge(firstSourcePdf, 1, firstSourcePdf.getNumberOfPages());
//Add pages from the second pdf document
PdfDocument secondSourcePdf = new PdfDocument(new PdfReader(SRC2));
merger.merge(secondSourcePdf, 1, secondSourcePdf.getNumberOfPages());
firstSourcePdf.close();
secondSourcePdf.close();
pdf.close();
(C06E04_88th_Oscar_Combine method createPdf)
Depending on your use case, you might want to use the PdfDenseMerger with its helper class PageVerticalAnalyzer instead of the PdfMerger here. It attempts to put content from multiple source pages onto a single target page and corresponds to the iText 5 PdfVeryDenseMergeTool from this answer. Due to the nature of PDF files this only works for PDFs without headers, footers, and similar artifacts.
I found a solution that works quite well.
public byte[] Combine(IEnumerable<byte[]> pdfs)
{
using (var writerMemoryStream = new MemoryStream())
{
using (var writer = new PdfWriter(writerMemoryStream))
{
using (var mergedDocument = new PdfDocument(writer))
{
var merger = new PdfMerger(mergedDocument);
foreach (var pdfBytes in pdfs)
{
using (var copyFromMemoryStream = new MemoryStream(pdfBytes))
{
using (var reader = new PdfReader(copyFromMemoryStream))
{
using (var copyFromDocument = new PdfDocument(reader))
{
merger.Merge(copyFromDocument, 1, copyFromDocument.GetNumberOfPages());
}
}
}
}
}
}
return writerMemoryStream.ToArray();
}
}
Use
DirectoryInfo d = new DirectoryInfo(INPUT_FOLDER);
var pdfList = new List<byte[]> { };
foreach (var file in d.GetFiles("*.pdf"))
{
pdfList.Add(File.ReadAllBytes(file.FullName));
}
File.WriteAllBytes(OUTPUT_FOLDER + "\\merged.pdf", Combine(pdfList));
Autor:
https://www.nikouusitalo.com/blog/combining-pdf-documents-using-itext7-and-c/
If you want to add two array of bytes and return one array of bytes as PDF/A
public static byte[] mergePDF(byte [] first, byte [] second) throws IOException {
// Initialize PDF writer
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
PdfWriter writer = new PdfWriter(arrayOutputStream);
// Initialize PDF document
PdfADocument pdf = new PdfADocument(writer, PdfAConformanceLevel.PDF_A_1B, new PdfOutputIntent("Custom", "",
"https://www.color.org", "sRGB IEC61966-2.1", new FileInputStream("sRGB_CS_profile.icm")));
PdfMerger merger = new PdfMerger(pdf);
//Add pages from the first document
PdfDocument firstSourcePdf = new PdfDocument(new PdfReader(new ByteArrayInputStream(first)));
merger.merge(firstSourcePdf, 1, firstSourcePdf.getNumberOfPages());
//Add pages from the second pdf document
PdfDocument secondSourcePdf = new PdfDocument(new PdfReader(new ByteArrayInputStream(second)));
merger.merge(secondSourcePdf, 1, secondSourcePdf.getNumberOfPages());
firstSourcePdf.close();
secondSourcePdf.close();
writer.close();
pdf.close();
return arrayOutputStream.toByteArray();
}
The question doesn't specify the language, so I'm adding an answer using C#; this works for me. I'm creating three separate but related PDFs then combining them into one.
After creating the three separate PDF docs and adding data to them, I combine them this way:
PdfDocument pdfCombined = new PdfDocument(new PdfWriter(destCombined));
PdfMerger merger = new PdfMerger(pdfCombined);
PdfDocument pdfReaderExecSumm = new PdfDocument(new PdfReader(destExecSumm));
merger.Merge(pdfReaderExecSumm, 1, pdfReaderExecSumm.GetNumberOfPages());
PdfDocument pdfReaderPhrases = new PdfDocument(new PdfReader(destPhrases));
merger.Merge(pdfReaderPhrases, 1, pdfReaderPhrases.GetNumberOfPages());
PdfDocument pdfReaderUncommonWords = new PdfDocument(new PdfReader(destUncommonWords));
merger.Merge(pdfReaderUncommonWords, 1, pdfReaderUncommonWords.GetNumberOfPages());
pdfCombined.Close();
So the combined PDF is a PDFWriter type of PdfDocument, and the merged pieces parts are PdfReader types of PdfDocuments, and the PdfMerger is the glue that binds it all together.
Here is the minimum C# code needed to merge file1.pdf into file2.pdf creating new merged.pdf:
var path = #"C:\Temp\";
var src0 = System.IO.Path.Combine(path, "merged.pdf");
var wtr0 = new PdfWriter(src0);
var pdf0 = new PdfDocument(wtr0);
var src1 = System.IO.Path.Combine(path, "file1.pdf");
var fi1 = new FileInfo(src1);
var rdr1= new PdfReader(fi1);
var pdf1 = new PdfDocument(rdr1);
var src2 = System.IO.Path.Combine(path, "file2.pdf");
var fi2 = new FileInfo(src2);
var rdr2 = new PdfReader(fi2);
var pdf2 = new PdfDocument(rdr2);
var merger = new PdfMerger(pdf0);
merger.Merge(pdf1, 1, pdf1.GetNumberOfPages());
merger.Merge(pdf2, 1, pdf2.GetNumberOfPages());
merger.Close();
pdf0.Close();
Here is a VB.NET solution using open source iText7 that can merge multiple PDF files to an output file.
Imports iText.Kernel.Pdf
Imports iText.Kernel.Utils
Public Function Merge_PDF_Files(ByVal input_files As List(Of String), ByVal output_file As String) As Boolean
Dim Input_Document As PdfDocument = Nothing
Dim Output_Document As PdfDocument = Nothing
Dim Merger As PdfMerger
Try
Output_Document = New iText.Kernel.Pdf.PdfDocument(New iText.Kernel.Pdf.PdfWriter(output_file))
'Create the output file (Document) from a Merger stream'
Merger = New PdfMerger(Output_Document)
'Merge each input PDF file to the output document'
For Each file As String In input_files
Input_Document = New PdfDocument(New PdfReader(file))
Merger.Merge(Input_Document, 1, Input_Document.GetNumberOfPages())
Input_Document.Close()
Next
Output_Document.Close()
Return True
Catch ex As Exception
'catch Exception if needed'
If Input_Document IsNot Nothing Then Input_Document.Close()
If Output_Document IsNot Nothing Then Output_Document.Close()
File.Delete(output_file)
Return False
End Try
End Function
USAGE EXAMPLE:
Dim success as boolean = false
Dim input_files_list As New List(Of String)
input_files_list.Add("c:\input_PDF1.pdf")
input_files_list.Add("c:\input_PDF2.pdf")
input_files_list.Add("c:\input_PDF3.pdf")
success = Merge_PDF_Files(input_files_list, "c:\output_PDF.pdf")
'Optional: handling errors'
if success then
'Files merged'
else
'Error merging files'
end if

file not downloading properly

I am downloading a file from a url and saving it to a directory on my phone.
the path is: /private/var/mobile/Applications/17E4F0B0-0781-4259-B39D-37057D44B778/Documents/samplefile.txt
However, when i debug the file is created and downloaded. But, when i ad-hoc it and run the file. samplefile.txt is created but it's blank.
Code:
String directory = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
var filename = Path.Combine (directory, "samplefile.txt");
if (!File.Exists (filename)) {
File.Create (filename);
var webClient = new WebClient ();
webClient.DownloadStringCompleted += (s, e) => {
var text = e.Result; // get the downloaded text
File.WriteAllText (filename, text);
};
var url = new Uri (/**myURL**/);
webClient.Encoding = Encoding.UTF8;
webClient.DownloadStringAsync (url);
I modified your sample slightly and the following works for me.
The StreamReader is only there just to re-read in the contents of the file to confirm that its the same contents in the file as that of the downloaded file:-
If you put a breakpoint there also you can manually inspect same contents as downloaded.
string directory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
var filename = Path.Combine(directory, "samplefile.txt");
if (!File.Exists(filename))
{
var webClient = new WebClient();
webClient.DownloadStringCompleted += (s, e) =>
{
// Write contents of downloaded file to device:-
var text = e.Result; // get the downloaded text
StreamWriter sw = new StreamWriter(filename);
sw.Write(text);
sw.Flush();
sw.Close();
sw = null;
// Read in contents from device and validate same as downloaded:-
StreamReader sr = new StreamReader(filename);
string strFileContentsOnDevice = sr.ReadToEnd();
System.Diagnostics.Debug.Assert(strFileContentsOnDevice == text);
};
var url = new Uri("**url here**, UriKind.Absolute);
webClient.Encoding = Encoding.UTF8;
webClient.DownloadStringAsync(url);
}

How to get Device Id in Windows 8 Metro app

How to get the unique Device in Windows Store App (Metro App)?
Can we use:
Windows.System.Profile.HardwareIdentification.GetPackageSpecificToken(null);
Windows.System.Profile.HardwareToken hToke = Windows.System.Profile.HardwareIdentification.GetPackageSpecificToken(null);
IBuffer hardwareId = hToke.Id;
IBuffer signature = hToke.Signature;
IBuffer certificate = hToke.Certificate;
DataReader reader = Windows.Storage.Streams.DataReader.FromBuffer(hardwareId);
byte[] ar = new Byte[hardwareId.Length];
reader.ReadBytes(ar);
string i = ar.ToString();
string id = System.Text.Encoding.Unicode.GetString(ar, 0, ar.Length);
System.Diagnostics.Debug.WriteLine("ID" + Convert.ToBase64String(ar));
Network adapter Id of first Network adapter found
IReadOnlyCollection<Windows.Networking.Connectivity.ConnectionProfile> profiles =
Windows.Networking.Connectivity.NetworkInformation.GetConnectionProfiles();
Windows.Networking.Connectivity.NetworkAdapter na = profiles.First<Windows.Networking.Connectivity.ConnectionProfile>().NetworkAdapter;
string nid = na.NetworkAdapterId.ToString();
Yes, this is a suggested way:
private string GetHardwareId()
{
var token = HardwareIdentification.GetPackageSpecificToken(null);
var hardwareId = token.Id;
var dataReader = Windows.Storage.Streams.DataReader.FromBuffer(hardwareId);
byte[] bytes = new byte[hardwareId.Length];
dataReader.ReadBytes(bytes);
return BitConverter.ToString(bytes);
}
Or, you have problems with this method?
Here is an other way I found:
Windows.Security.ExchangeActiveSyncProvisioning.EasClientDeviceInformation deviceInfo =
new Windows.Security.ExchangeActiveSyncProvisioning.EasClientDeviceInformation();
var myDeviceID = deviceInfo.Id;