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.