20

We have a customer portal where we allow our customers to upload several documents(PDF only) as required. These documents after getting uploaded to SFDC are then also sent to our other systems.

While this is working fine, we are observing some issues when users tries to upload signed PDFs, to be specific, PDFs signed using itext. The PDFs get uploaded successfully in SFDC, however while sending them from SFDC to our other systems, they fail.

I wanted to know if there is a way by which I can restrict our customers from uploading PDFs that are signed. OR even simply restricting them from uploading PDFs signed using itext.

NOTE: I understand this may not be the correct way to go with this problem, but until our other systems allow signed PDFs, I will have to restrict users from uploading them.

Thanks, Shailesh.

  • 1
    Where are you storing the PDFs? (I.e. in Documents or Content Libraries?) You might be able to use a trigger that detects a distinct MIME type to reject the files. Otherwise you would need to sniff the Blob data to try and figure out if it has been signed using itext. – Daniel Ballinger Oct 8 '13 at 22:08
  • I found some documentation on it, but I'd need to be able to experiment. Can you somehow upload a signed PDF for me to toy with? – sfdcfox Oct 10 '13 at 16:17
  • Unfortunately I do not have one handy with me. But this link has a sample signed PDF. Not sure if this helps you. Do let me know. blogs.adobe.com/security/SampleSignedPDFDocument.pdf – Shailesh Deshpande Oct 10 '13 at 16:31
  • I might be able to work with this, actually – sfdcfox Oct 10 '13 at 17:16
27
+50

PDF files are binary files, and as such, we can't just load it up into a string directly and examine the data to determine if a signature marker is present; it would probably require too much processing time on all but simple PDFs anyways (those less than about 1mb). However, we can check against a simple heuristic that will tell us, with fair likelihood, if a file is signed or not.

A signed PDF will contain one of the following textual signatures within the code:

/adbe.pkcs7.sha1
/adbe.x509.rsa_sha1
/adbe.pkcs7.detached
/ETSI.CAdES.detached
/ETSI.RFC3161

Since we can't work with binary data, we're forced to work in Base64 text to try and decipher the code. This means that the code will be in one of three possible Base64 positions. Based on that information, we could look for the base64 encoded values of:

/ETSI.RFC316
ETSI.RFC3161
TSI.RFC31
/ETSI.CAdES.detach
ETSI.CAdES.detache
TSI.CAdES.detached
/adbe.pkcs7.detach
adbe.pkcs7.detache
dbe.pkcs7.detached
/adbe.pkcs7.sha
adbe.pkcs7.sha1
dbe.pkcs7.sh
/adbe.x509.rsa_sha
adbe.x509.rsa_sha1
dbe.x509.rsa_sh

Each signature is necessary because base64 bytes are not aligned with real bytes, so there are three possible combinations for strings we have to find. This is because the bytes we can detect must be byte aligned:

[Pre] [Detection  Range] [Post]
  123 123123123123123123 123
      /adbe.pkcs7.detach ed
    / adbe.pkcs7.detache d
   /a dbe.pkcs7.detached

Here are the relevant base64 signatures we can scan (from above) in base64:

L0VUU0kuUkZDMzE2
RVRTSS5SRkMzMTYx
VFNJLlJGQzMx
L0VUU0kuQ0FkRVMuZGV0YWNo
RVRTSS5DQWRFUy5kZXRhY2hl
VFNJLkNBZEVTLmRldGFjaGVk
L2FkYmUucGtjczcuZGV0YWNo
YWRiZS5wa2NzNy5kZXRhY2hl
ZGJlLnBrY3M3LmRldGFjaGVk
L2FkYmUucGtjczcuc2hh
YWRiZS5wa2NzNy5zaGEx
ZGJlLnBrY3M3LnNo
L2FkYmUueDUwOS5yc2Ffc2hh
YWRiZS54NTA5LnJzYV9zaGEx
ZGJlLng1MDkucnNhX3No

We can't determine the bits before or after accurately, because they are not byte (and therefore character) aligned. We could, of course, check the bounding characters (4 on either side) and decode them to make sure that the appropriate strings (any in the first list) appear completely, but this is probably not necessary, since those strings are fairly uncommon in normal text (unless, of course, you're talking about a paper on signed PDFs!).

So, a probably sufficient detection algorithm would go along the lines of this:

Set<String> signatures = new Set<String> {
    'L0VUU0kuUkZDMzE2','RVRTSS5SRkMzMTYx','VFNJLlJGQzMx','L0VUU0kuQ0FkRVMuZGV0YWNo','RVRTSS5DQWRFUy5kZXRhY2hl',
    'VFNJLkNBZEVTLmRldGFjaGVk','L2FkYmUucGtjczcuZGV0YWNo','YWRiZS5wa2NzNy5kZXRhY2hl','ZGJlLnBrY3M3LmRldGFjaGVk',
    'L2FkYmUucGtjczcuc2hh','YWRiZS5wa2NzNy5zaGEx','ZGJlLnBrY3M3LnNo','L2FkYmUueDUwOS5yc2Ffc2hh',
    'YWRiZS54NTA5LnJzYV9zaGEx','ZGJlLng1MDkucnNhX3No'
};
String blobAsString = EncodingUtil.base64Encode(theAttachment.Body);
Boolean isSigned = false;
for(String signature: signatures) {
    isSigned = blobAsString.indexOf(signature) != -1;
    if(isSigned) {
        break;
    }
}
if(isSigned) {
    // stop upload from happening
}

I tested this against two PDF files in my developer org. The first was a PDF that was an old paycheck PDF from my employer, the other the test signature file from the original post comments.

In my test code, the results are as follows:

"Unsigned Test PDF":  USER_DEBUG [20]|ERROR|false
"Signed Test PDF": USER_DEBUG [20]|ERROR|true

In other words, detection was successful with 100% accuracy, but this is only a small sample of data. I would consider putting a logging mode in for a month or so, log the results of this scan, and see how many false positives/negatives that you get. You may need to fine-tune the algorithm if you find many false results.

You could probably also simply scan for the following signatures:

/ad
adb
dbe
/ET
ETS
TSI

Then scan backwards until you find the /SubFilter marker, and make sure the format is what is expected, then scan forward to make sure it actually matches one of the target strings that identifies a signature. That logic would be even more accurate, but slower; I chose to go with a simple algorithm that should pass with over 99% confidence in normal texts.

I found the target strings from the document that explains signing: iTextPDF signatures.

  • Thanks. I shall check this and let you know. Appreciate your help. – Shailesh Deshpande Oct 11 '13 at 14:48
  • Impressive stuff, nice one! – Andrew Fawcett Oct 16 '13 at 9:57
  • That reminds me, I next need to figure out how to decompress ZIP files now that we have "unlimited" script statements :p – sfdcfox Oct 16 '13 at 15:59
  • Thanks. It worked for me. I tested out for around 6 - 7 PDFs both signed and unsigned and the code worked as expected. As you suggested, I will keep debug open for next few days to verify the correctness. One change I had to make here was to use EncodingUtil.base64Encode() instead of EncodingUtil.base64Decode(). – Shailesh Deshpande Oct 17 '13 at 4:33
  • Oh, of course. I fixed that in my dev org but forgot to update it here. – sfdcfox Oct 17 '13 at 13:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.