In order to add docuemnt the timestamp, using itext, we have com.itextpdf.text.pdf.security.TSAClient abstract interface.
but how to get TimeStamp Certificate from the PDF file?
You can call the AcroFields method verifySignature for signature fields containing document timestamps, too, and query its result for the certificates.
PdfReader reader = ...;
AcroFields fields = reader.getAcroFields();
List<String> names = fields.getSignatureNames();
String signatureName = names.get(names.size() - 1);
PdfPKCS7 pkcs7 = fields.verifySignature(signatureName);
System.out.println(String.format("Checking %ssignature %s", pkcs7.isTsp() ? "document-level timestamp " : "", signatureName));
Certificate[] chain = pkcs7.getSignCertificateChain();
This code inspects the outermost signature or document time stamp of a document and returns that signature's or time stamp's certificate chain as far as it is known.
You might want to look at the LtvVerifier class (from which I extracted the code above) for verification of all signatures and document timestamps and read section 5.4.4 Validating an LTV document in Digital Signatures for PDF documents for the use in context.
Related
I'm trying to sign a pdf using PCKS11 (USB Token) and iText, It works fine when I use SHA-1 algorithm, the signature is valid and everything works fine however when I change it to SHA256 the signature is not valid, Does anyone know why? I'll put my code below:
PKCS11 pkcs11 = PKCS11.getInstance("C:\\Windows\\System32\\example.dll", "C_GetFunctionList", null,false);
long[] slotList = pkcs11.C_GetSlotList(true);
String providerString = "name=*\nlibrary=C:\\Windows\\System32\\example.dll\n" + "slot=" + slotList [0];
SunPKCS11 sunPKCS11 = new SunPKCS11(new ByteArrayInputStream(providerString .getBytes()));
Provider provider = sunPKCS11;
KeyStore keyStore = KeyStore.getInstance("PKCS11", provider);
keyStore.load(null, password);
keyStore.aliases();
Security.addProvider(keyStore.getProvider());
List<String> aliases = Collections.list(keyStore.aliases());
String alias = aliases.get(0);
PrivateKey pk = (PrivateKey)keyStore.getKey(alias,password);
Certificate[] certChain = new Certificate[1];
certChain[0] = signerCert;
PdfReader reader = new PdfReader(inputData);
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
PdfStamper stamper = PdfStamper.createSignature(reader, outStream, '\0',null,true);
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
String fieldName = keyStore.getCertificateAlias(signerCert).replaceAll(".","");
appearance.setVisibleSignature(new Rectangle(420, 10, 70, 85), pageNumber,fieldName);
ExternalSignature es = new PrivateKeySignature(pk, DigestAlgorithms.SHA256,
keyStore.getProvider().getName());
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(appearance,
digest, es,
certChain, null,
null, null,
0, CryptoStandard.CADES);
return outStream.toByteArray();
Why does it work with SHA-1 and not SHA-2? Where do you think the problem arises?
There are mismatches and errors in your signature.
download.pdf
This is the example file you shared in a comment to your question
Mismatch of Document Digest
The SHA-256 hash value of the signed bytes of your PDF is
9356BCD36F172806A3DCE7F062A66441E7C1DDC9203ABDAA0154A3F19208C8E3
but the embedded signature container claims it to be
5F892978FF2459157D631809A05F5DBCFCB55800236D2D5C3E4E4D94577012B4
According to your code that should not happen. Are you sure that PDF has been created by the code in your question? Or have you somehow changed it as you indicated by your I changed my hash like this comment and created the example document thereafter? Then this discrepancy most likely is caused by your change.
Broken Raw RSA Signature
After decrypting the raw signature bytes and removing the padding one gets
3031300D0609608648016503040201050004201DC71B824BAA3C7EC6744A0941CFADDAA893E8C1
This is incomplete. It corresponds to
SEQUENCE (2 elem)
SEQUENCE (2 elem)
OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.1 sha-256 (NIST Algorithm)
NULL
OCTET STRING (32 byte) 1DC71B824BAA3C7EC6744A0941CFADDAA893E8C1XXXXXXXXXXXXXXXXXXXXXXXX
with the 12 bytes for that XXXXXXXXXXXXXXXXXXXXXXXX piece missing.
Thus, it looks like the signing device does not really support SHA256withRSA, at least not in combination with the example.dll PKCS#11 driver and the SunPKCS11 security provider.
As an aside, the OCTET STRING there should have been
EC7FCC5D003DFEC58B0ECB49CEEAD28495FFA8D798A1A88DA6051C1857B971EC
Thus, it looks like here is another mismatch, the 1DC71B824BAA3C7EC6744A0941CFADDAA893E8C1 actually there appears not to be related to this value.
SHA256.pdf
Can you check out this one? this one is made with itext's library and no code was changed.
In this file there indeed are no hash mismatches anymore, merely the signature value is cut off, here
3031300D0609608648016503040201050004204C8440B547E6A0EFD1489B8F5B5DFDA2DFA45DC1
corresponding to
SEQUENCE (2 elem)
SEQUENCE (2 elem)
OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.1 sha-256 (NIST Algorithm)
NULL
OCTET STRING (32 byte) 4C8440B547E6A0EFD1489B8F5B5DFDA2DFA45DC1XXXXXXXXXXXXXXXXXXXXXXXX
The complete OCTET STRING should have been
4C8440B547E6A0EFD1489B8F5B5DFDA2DFA45DC19048B5E53D468FE6A8E4E973
So apparently indeed the chain of your signing device, the example.dll PKCS#11 driver, and the SunPKCS11 security provider from your JRE does not support SHA256withRSA.
I am trying to create a new KPI in the Digests model to show the number of new customers created per week. (Unfortunately, this functionality is not well documented).
As documented, I have created two fields in the digest model:
x_studio_kpi_new_customers (Boolean)
x_studio_kpi_new_customers_value (Integer)
The value is
for record in self:
start, end, company = record._get_kpi_compute_parameters()
record.x_studio_kpi_new_customers_value = sum(self.env['res.partner'].search([
('x_studio_when', '>=', start),
('x_studio_when', '<', end)
]).mapped('x_studio_counter'))
x_studio_counter is just the value 1 in all records
x_studio_when is the record creation date (have also tried with a datetime field)
I have also tried the code below:
for record in self:
start, end, company = record._get_kpi_compute_parameters()
new_customers = self.env['res.partner'].search_count([('x_studio_when', '>=', start), ('x_studio_when', '<', end)])
record['x_studio_kpi_new_customers_value'] = new_customers
I keep getting 0.
Any help will be appreciated.
In order to build your customized digest, follow these steps:
You may want to add new computed fields with Odoo Studio:
You must create 2 fields on the digest object:
first create a boolean field called kpi_myfield and display it in the KPI's tab;
then create a computed field called kpi_myfield_value that will compute your customized KPI.
Create below "compute_kpis_actions" method and after that digest mail able to view count.
def compute_kpis_actions(self, company, user):
res = super(Digest, self).compute_kpis_actions(company, user)
res['x_studio_kpi_new_customers'] = 'your_module_name.your_action_name&menu_id=%s' % self.env.ref(your_module_name.your_menu_name').id
return res
I have a large logging and data entry program that receives DataTable object with 1 DataRow. The DataTable have random amount of columns, with random column name and type so i cannot have class for each. Using these DataColumn i get the DataType and build a BsonDocument from scratch out of this.
Here's a short example
public void ParseData(DataTable table)
{
// create the document
var document = new BsonDocument();
// get the only row in the table
var row = table.Rows[0];
// for each column we add the property
foreach (DataColumn column in table.Columns)
{
// create an empty value
BsonValue value = null;
// current column value
var columnValue = row[column.ColumnName];
// set the value based on the datatype
if (column.DataType == typeof(string)) value = new BsonString(columnValue.ToString());
else if (column.DataType == typeof(int)) value = new BsonInt32(Convert.ToInt32(columnValue));
else if (column.DataType == typeof(float)) value = new BsonDouble(Convert.ToDouble(columnValue));
else if (column.DataType == typeof(double)) value = new BsonDouble(Convert.ToDouble(columnValue));
else if (column.DataType == typeof(bool)) value = new BsonBoolean(Convert.ToBoolean(columnValue));
else if (column.DataType == typeof(DateTime)) value = new BsonDateTime(Convert.ToDateTime(columnValue));
// add the element
document.Add(new BsonElement(column.ColumnName, value));
}
// insert the document in the generic collection
InsertDocument(document);
}
As you can see it's pretty simple. I have removed a lot of types in the list as we have many custom types that might pass so i just kept the basic ones. The problem is that i cannot figure out how to force the BsonDateTime to save as local time in the collection. When doing filters with legacy apps it's not working. I need them to be saved as local time. It's never been an issue in the past but because of those legacy apps from the early 90's that still need support i have to figure something out.
I also need to reload them as local time. If i could, i would save them as string but i can't because since all columns are random i do not know when loading if a specific BsonString is really a string or if it's a DateTime. For reloading i must not reload really as local time. I must reload the exact value in the database. I only control the creation of the document. But reading i only control a few one's that will be reading from it that are in C#, Java and C++. The rest are legacy apps that companies doesn't even exist anymore.
I did try to just modify every single date that came in the system to account for UTC and change the date to when saved as UTC it's stored property and filters from legacy apps still works but all of .NET, Java and C++ apps load up the wrong value and not the written value.
Is there a way to just disable UTC in a specific collection or database in MongoDB directly like you can in SQL server ?
MongoDB stores times in UTC and does not have time zone support. You can store any values you like but they will be interpreted as UTC timestamps by most MongoDB-related software.
I am using a smart card that is signing a SHA-1 hash of a document, and compute a 256 bytes digital signature.
I am using the code posted on this question - iText signing PDF using external signature with smart card.
My problem is that I get the error:" The document has been altered or corrupted since the signature was applied".
I am using a GUI to create the hash and then send the signed 256 bytes that is computed on the card to the signing functions .
Here is my code:
hash creating code of filepath pdf document:
SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
SHA256 sha2 = SHA256.Create();
//sha2.ComputeHash
byte[] pdfBytes = System.IO.File.ReadAllBytes(filePath);
byte[] hash = null;
hash= sha1.ComputeHash(pdfBytes);
the above code is used in one of the GUI functions to create the hash of the document
namespace EIDSmartCardSign
{
class PdfSignature
{
private string outputPdfPath;
private string certPath;
byte[] messageDigest;
private string inputPdfPath;
public PdfSignature(byte[] messageDigest, string inputPdfPath,string outputPdfPath)
{
this.messageDigest = messageDigest;
this.outputPdfPath = outputPdfPath;
this.inputPdfPath = inputPdfPath;
}
public void setCertPath(string certPath)
{
this.certPath = certPath;
}
public void signPdf()
{
X509Certificate2 cert = new X509Certificate2();
cert.Import(certPath); // .cer file certificate obtained from smart card
X509CertificateParser certParse = new Org.BouncyCastle.X509.X509CertificateParser();
Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[1] ;
chain[0] = certParse.ReadCertificate(cert.RawData);
X509Certificate2[] certs;
PdfReader reader = new PdfReader(inputPdfPath);
FileStream fout = new FileStream(outputPdfPath,FileMode.Create);
PdfStamper stamper = PdfStamper.CreateSignature(reader, fout, '\0',null,true);
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SignatureCreator = "Me";
appearance.Reason = "Testing iText";
appearance.Location = "On my Laptop";
iTextSharp.text.Rectangle rec = new iTextSharp.text.Rectangle(50, 50, 250, 100);
appearance.SetVisibleSignature(rec, 1, "Signature");
IExternalSignature extSignature= new MyExternalSignature("SHA-1",this.messageDigest);
MakeSignature.SignDetached(appearance, extSignature, chain, null, null, null, 0, CryptoStandard.CMS);
//MakeSignature.
}
}
}
Your hash creating function
SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
SHA256 sha2 = SHA256.Create();
//sha2.ComputeHash
byte[] pdfBytes = System.IO.File.ReadAllBytes(filePath);
byte[] hash = null;
hash = sha1.ComputeHash(pdfBytes);
calculates the wrong hash value.
Have a look at this answer on Information Security Stack Exchange, in particular the sketch
shows that to get the document bytes to sign you do not take the original PDF but instead have to prepare it for integrating the signature container (add signature field, field value with some space reserved for the signature container, and field visualization) and then hash all the bytes except the space reserved for the signature container.
Furthermore, even this naked hash is not the data to sign. Instead a set of attributes is built, one of them containing the document hash calculated as above, other ones containing references to the signer certificate etc., and these attributes are to be signed.
Thus, instead do what you already claimed to be doing:
I am using the code posted on this question - iText signing PDF using external signature with smart card.
In particular the code there does not sign the hash of the whole file but instead uses the data the method Sign of the IExternalSignature implementation receives as parameter which is constructed as explained above.
More details
In a comment the OP said
The card I am working with expects a 20 bytes hash.
20 bytes would be typical for a naked hash generated by either SHA1 or RIPEMD-160. According to your question text, I assume the former algorithm is used. (This by the way indicates that the context does not require a high security level as SHA1 effectively is already phased out or in the process of being phased out for such use cases.)
What steps are needed to further create this hash After hashing the contents of the pdf?
Simply do as in the IExternalSignature implementation in the question you referenced:
public virtual byte[] Sign(byte[] message) {
byte[] hash = null;
using (SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider())
{
hash = sha1.ComputeHash(message);
}
byte[] sig = MySC.GetSignature(hash);
return sig;
}
(Obviously chances are that your smart card signing routine is not called MySC.GetSignature and you have to replace that call accordingly...)
As your card appears to expect a naked hash value in contrast to the card of the OP of the referenced question, this should work for you.
Where can I find examples of creating the aformentioned integrated signature container?
In the examples to the iText white paper Digital Signatures for PDF Documents.
After the signature process, I have 256 bytes signed data, 3 .cer certificates exported from the card.
256 bytes signed data sounds like a naked signature generated using RSA or RSASSA-PSS with a 2048 bit key size.
That been said, you need the signer certificate before signing: In most relevant profiles the signed attributes have to contain a reference to the signer certificate. In the code in the question you referenced that signer certificate is handled here
public void StartTest(){
X509Certificate2 cert = new X509Certificate2();
cert.Import("cert.cer"); // certificate obtained from smart card
X509CertificateParser certParse = new Org.BouncyCastle.X509.X509CertificateParser();
Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[] { certParse.ReadCertificate(cert.RawData) };
[...]
MyMakeSignature.SignDetached(appearance, externalSignature, chain, null, null, tsc, 0, CryptoStandard.CADES);
In particular you have to identify the correct signer certificate among those three certificate your card returns; otherwise you might have the same issue as the OP in the referenced question.
How do I create the Contents object when I have all of this data?
Considering what you said about your use case, chances are good that you really merely have to use the code posted of the question iText signing PDF using external signature with smart card with minor adaptions.
How can one find where are lines located in a document with iText?
Suppose say I have a table in a PDF document, and want to read its contents; I would like to find where exactly the cells are located. In order to do that I thought I might find the intersections of lines.
I think your only option using iText will be to parse the PDF tokens manually. Before doing that I would have a copy of the PDF spec handy.
(I'm a .Net guy so I use iTextSharp but other than some capitalization differences and property declarations they're almost 100% the same.)
You can get the individual tokens using the PRTokeniser object which you feed bytes into from calling getPageContent(pageNum) on your PdfReader.
//Get bytes for page 1
byte[] pageBytes = reader.getPageContent(1);
//Get the tokens for page 1
PRTokeniser tokeniser = new PRTokeniser(pageBytes);
Then just loop through the PRTokeniser:
PRTokeniser.TokType tokenType;
string tokenValue;
while (tokeniser.nextToken()) {
tokenType = tokeniser.tokenType;
tokenValue = tokeniser.stringValue;
//...check tokenValue, do something with it
}
As far a tokenValue, you'd want to probably look for re and l values for rectangle and line. If you see an re then you want to look at the previous 4 values and if you see an l then previous 2 values. This also means that you need to store each tokenValue in an array so you can look back later.
Depending on what you used to create the PDF with you might get some interesting results. For instance, I created a 4 cell table with Microsoft Word and saved as a PDF. For some reason there are two sets of 10 rectangles with many duplicates, but the general idea still works.
Below is C# code targeting iTextSharp 5.1.1.0. You should be able to convert it to Java and iText very easily, I noted the one line that has .Net-specific code that needs to be adjusted from a Generic List (List<string>) to a Java equivalent, probably an ArrayList. You'll also need to adjust some casing, .Net uses Object.Method() whereas Java uses Object.method(). Lastly, .Net accesses properties without gets and sets, so Object.Property is both the getter and setter compared to Java's Object.getProperty and Object.setProperty.
Hopefully this gets you started at least!
//Source file to read from
string sourceFile = "c:\\Hello.pdf";
//Bind a reader to our PDF
PdfReader reader = new PdfReader(sourceFile);
//Create our buffer for previous token values. For Java users, List<string> is a generic list, probably most similar to an ArrayList
List<string> buf = new List<string>();
//Get the raw bytes for the page
byte[] pageBytes = reader.GetPageContent(1);
//Get the raw tokens from the bytes
PRTokeniser tokeniser = new PRTokeniser(pageBytes);
//Create some variables to set later
PRTokeniser.TokType tokenType;
string tokenValue;
//Loop through each token
while (tokeniser.NextToken()) {
//Get the types and value
tokenType = tokeniser.TokenType;
tokenValue = tokeniser.StringValue;
//If the type is a numeric type
if (tokenType == PRTokeniser.TokType.NUMBER) {
//Store it in our buffer for later user
buf.Add(tokenValue);
//Otherwise we only care about raw commands which are categorized as "OTHER"
} else if (tokenType == PRTokeniser.TokType.OTHER) {
//Look for a rectangle token
if (tokenValue == "re") {
//Sanity check, make sure we have enough items in the buffer
if (buf.Count < 4) throw new Exception("Not enough elements in buffer for a rectangle");
//Read and convert the values
float x = float.Parse(buf[buf.Count - 4]);
float y = float.Parse(buf[buf.Count - 3]);
float w = float.Parse(buf[buf.Count - 2]);
float h = float.Parse(buf[buf.Count - 1]);
//..do something with them here
}
}
}