I am creating a PADES signature using pdfbox 3.0.0 RC, my code works using the example to create the digital signature. However, I am unable to see the signature level in Adobe Acrobat when I open the document with this tool although it is able to validate my signature.
I am not creating the VRI so I am guessing that this might be an issue but then if this is necessary to validate my signature I don't understand why the signature is displayed as valid?
Adobe Acrobat Signature:
/**
* Service for automatically signing a document as part of a workflow. In this instance no user information is
* gathered
*
* #param taskID
* #param processName which will be added to the document
* #param keyID the ID for the key used to sign the PDF document
* #return the signed PDF document as a base 64 Encoded String
*/
#Transactional
public String signPDFService(String processID,
String processName,
String keyID,
ObjectData signatureImage,
String creator)
{
try {
ByteArrayOutputStream ostream = new ByteArrayOutputStream();
//Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(documents.get(0).getProcessID());
/*List<Task> tasks = taskService.createTaskQuery()
.taskId(taskID)
.list();
// Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(documents.get(0).getProcessID());
List<PDFDocument> documents = tasks.stream()
.map(task -> {
String processID = task.getProcessInstanceId();
Map<String, Object> variables = taskService.getVariables(task.getId());
PDFDocument document;
try {
document = new PDFDocument(
(ArrayList) variables.get("assigneeList"),
(String) variables.get("unsignedPDFDocument"),
task.getProcessInstanceId(),
task.getId(),
(String) variables.get("name"),
(String) variables.get("description")
);
document.setHistory((ArrayList) variables.get("history"));
return document;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
} catch (CMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
})
.collect(Collectors.toList());*/
//Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(documents.get(0).getProcessID());
Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(processID);
if(pdfDocumentProcessInstance.isEmpty())
throw new IOException("No process found");
String pdfDocumentBase64String = pdfDocumentProcessInstance.get().getAttributes().get("PDFDocument");
String extractedPDFString = pdfDocumentBase64String.replaceAll("data:application/pdf;base64,", "").replaceAll("data:;base64,", "").replaceAll("data:application/octet-stream", "");
//String extractedPDFString = base64PDF.replaceAll("data:application/pdf;base64,", "").replaceAll("data:;base64,", "");
InputStream stream = new ByteArrayInputStream(Base64.getDecoder().decode(extractedPDFString.getBytes()));
//Create the date object to sign the document
Date date = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
//Retrieve certificate chain for the PDF Signer
String certChainPEM = kmsService.getCertChainPEM(keyID);
X509Certificate pdfSignerCertificate = X509Utils.readCertificateChain(certChainPEM).get(0).getCertificate();
//Create the CMS Signing Object
ExternalSignatureCMSSignedDataGenerator cmsGenerator = new ExternalSignatureCMSSignedDataGenerator();
ExternalSignatureSignerInfoGenerator signerGenerator = new ExternalSignatureSignerInfoGenerator(CMSSignedDataGenerator.DIGEST_SHA256, "1.2.840.10045.4.3.2");
signerGenerator.setCertificate(pdfSignerCertificate);
ExternalSigningSupport externalSigningSupport;
PDDocument pdDocument = Loader.loadPDF(stream);
//Create the PDFBox Signature Object
PDSignature pdSignature = new PDSignature();
pdSignature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
pdSignature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
pdSignature.setLocation("Remote IS Blocks Signer");
pdSignature.setName("IS Blocks Signer");
pdSignature.setReason(processName);
pdDocument.setDocumentId(calendar.getTimeInMillis());
pdSignature.setSignDate(calendar);
// Optional: Certify the first time signature
// can be done only if version is at least 1.5 and if not already set
// doing this on a PDF/A-1b file fails validation by Adobe preflight (PDFBOX-3821)
// PDF/A-1b requires PDF version 1.4 max, so don't increase the version on such files.
int accessPermissions = SigUtils.getMDPPermission(pdDocument);
if (pdDocument.getVersion() >= 1.5f && accessPermissions == 0 && processName.contains("Document Certifying Key"))
{
logger.debug("Certifying Document");
SigUtils.setMDPPermission(pdDocument, pdSignature, 3);
}
if(signatureImage != null) {
String data = signatureImage.getAttributes().get("data").replaceAll("data:application/pdf;base64,", "").replaceAll("data:;base64,", "").replaceAll("data:image/png;base64,", "");
int pageNumber = Integer.parseInt(signatureImage.getAttributes().get("page"));
float x = Float.parseFloat(signatureImage.getAttributes().get("x"));
float y = Float.parseFloat(signatureImage.getAttributes().get("y"));
float width = Float.parseFloat(signatureImage.getAttributes().get("width"));
float height = Float.parseFloat(signatureImage.getAttributes().get("height"));
SignatureOptions signatureOptions;
// register signature dictionary and sign interface
signatureOptions = new SignatureOptions();
PDFVisibleSignature pdfVisibleSignature = new PDFVisibleSignature();
signatureOptions.setVisualSignature(pdfVisibleSignature.createVisualSignatureTemplate(
x,
y,
width,
height,
pdDocument,
pageNumber,
pdSignature,
Base64.getDecoder().decode(data.getBytes("UTF-8"))));
signatureOptions.setPage(pageNumber);
pdDocument.addSignature(pdSignature, null, signatureOptions);
} else {
pdDocument.addSignature(pdSignature);
}
externalSigningSupport = pdDocument.saveIncrementalForExternalSigning(ostream);
//Create the message digest of the pre-signed PDF
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] bytes = org.apache.commons.io.IOUtils.toByteArray(externalSigningSupport.getContent());
byte[] hashBytes = digest.digest(bytes);
//CMS Signature
InputStream isBytes = new ByteArrayInputStream(bytes);
CMSProcessable input = new CMSProcessableInputStream(isBytes);
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
MessageDigest messageDigest1 = MessageDigest.getInstance("SHA-256");
byte[] hash = messageDigest1.digest(bytes);
byte[] bytesToSign = signerGenerator.getBytesToSign(PKCSObjectIdentifiers.data, hash, new Date(),
"BC");
String encodedData = Base64.getEncoder().encodeToString(bytesToSign);
logger.debug("Bytes to Sign:" + (Base64.getEncoder().encodeToString(bytesToSign)));
logger.debug("Hash:" + Base64.getEncoder().encodeToString(hash));
//Create the signature using the keyID
//At this time only ECDSAWithSHA256 is supported
Map<String, String> signature = kmsService.sign(keyID, encodedData);
byte[] signedBytes = Base64.getDecoder().decode(signature.get("signature"));
X509Certificate[] chain;
signerGenerator.setCertificate(pdfSignerCertificate);
signerGenerator.setSignedBytes(signedBytes);
cmsGenerator.addSignerInf(signerGenerator);
cmsGenerator.addCertificatesAndCRLs(X509Utils.getCertStore(signature.get("certificateChain")));
CMSSignedData signedData = cmsGenerator.generate(new CMSProcessableByteArray(hash), false);
//Add a RFC3161 Time Stamp
ValidationTimeStamp validation = new ValidationTimeStamp("https://freetsa.org/tsr");
signedData = validation.addSignedTimeStamp(signedData);
ContentSigner nonSigner = new ContentSigner() {
#Override
public byte[] getSignature() {
return signedBytes;
}
#Override
public OutputStream getOutputStream() {
return new ByteArrayOutputStream();
}
#Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return new DefaultSignatureAlgorithmIdentifierFinder().find( "SHA256WithECDSA" );
}
};
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build());
gen.addCertificate(new X509CertificateHolder(pdfSignerCertificate.getEncoded()));
sigb.setDirectSignature( true );
gen.addSignerInfoGenerator(sigb.build(nonSigner, new X509CertificateHolder(pdfSignerCertificate.getEncoded())));
CMSTypedData msg = new CMSProcessableInputStream( new ByteArrayInputStream( "not used".getBytes() ) );
CMSSignedData signedData1 = gen.generate((CMSTypedData)msg, false);
signedData1.getEncoded();
externalSigningSupport.setSignature(signedData.getEncoded());
//documents.get(0).addHistoricEvent("Signed " + processName);
//ArrayList<String> history = documents.get(0).getHistory();
//Post Signature
String signedPDFDocument = Base64.getEncoder().encodeToString(ostream.toByteArray());
PDDocument newPdf1;
newPdf1 = Loader.loadPDF(ostream.toByteArray());
byte[] fileContent = ostream.toByteArray();
List<PDSignature> pdfSignatures;
pdfSignatures = newPdf1.getSignatureDictionaries();
byte[] signatureAsBytes;
signatureAsBytes = newPdf1.getLastSignatureDictionary().getContents( fileContent );
byte[] signedContentAsBytes;
signedContentAsBytes = newPdf1.getLastSignatureDictionary().getSignedContent( fileContent );
// Now we construct a PKCS #7 or CMS.
CMSProcessable cmsProcessableInputStream = new CMSProcessableByteArray(signedContentAsBytes);
CMSSignedData cmsSignedData;
cmsSignedData = new CMSSignedData(cmsProcessableInputStream, signatureAsBytes);
Store certificatesStore = cmsSignedData.getCertificates();
Collection<SignerInformation> signers = cmsSignedData.getSignerInfos().getSigners();
SignerInformation signerInformation = signers.iterator().next();
Collection matches = certificatesStore.getMatches(signerInformation.getSID());
X509CertificateHolder certificateHolder = (X509CertificateHolder) matches.iterator().next();
ObjectMapper mapper = new ObjectMapper();
ArrayList<String> signatures = null;
//signatures = documents.get(0).getSignatures();
for(int iCount = 0; iCount < pdfSignatures.size(); iCount++) {
PDFSignature pdfSignature = new PDFSignature(
pdfSignatures.get(iCount).getName(),
pdfSignatures.get(iCount).getLocation(),
pdfSignatures.get(iCount).getSignDate().getDisplayName(Calendar.LONG_FORMAT, java.util.Calendar.LONG, Locale.UK),
pdfSignatures.get(iCount).getReason(),
certificateHolder.getSubject().toString(),
certificateHolder.getIssuer().toString(),
Base64.getEncoder().encodeToString(certificateHolder.getEncoded()));
//signatures.add(mapper.writeValueAsString(pdfSignature));
logger.info("Signature" + mapper.writeValueAsString(pdfSignature));
}
Map<String, Object> variables = new HashMap<String, Object>();
// variables.put("history", history);
//variables.put("unsignedPDFDocument", signedPDFDocument);
//variables.put("signatures", signatures);
//variables.put("status", value)
Map<String, String> pdfDocumentProcessInstanceAttributes = pdfDocumentProcessInstance.get().getAttributes();
pdfDocumentProcessInstanceAttributes.put("PDFDocument", signedPDFDocument);
ObjectData newpdfProcessInstance = pdfDocumentProcessInstance.get();
newpdfProcessInstance.setAttributes(pdfDocumentProcessInstanceAttributes);
userDataRepository.save(newpdfProcessInstance);
newpdfProcessInstance.getHistory().add(new Date() + "Signed by:" + creator);
System.out.println(newpdfProcessInstance.getId() + " " + newpdfProcessInstance.toString());
newPdf1.close();
ostream.close();
} catch (Exception e){
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
While analyzing the file document-with signingTime.pdf you provided in a comment, I recognized an issue in it. Being aware of that issue I re-checked your original document-17 21.08.14.pdf and also recognized that issue therein, so maybe this issue causes the validation problem you're here to solve. Thus, ...
Both your example files (document-17 21.08.14.pdf and document-with signingTime.pdf) contain each actually two concatenated copies of the same, multi-revision PDF with a single signature Signature1, merely the second copy has a changed ID entry. Added to them are incremental updates with a signature Signature2.
%PDF-1.4
...
15 0 obj
<<
/FT /Sig
...
/T (Signature1)
...
>>
endobj
...
/ID [<1952AB9C134E46B58251246E985D5C15> <7F887303DDC0ED7C37AE77403E30DFB0>]
...
%%EOF
%PDF-1.4
...
15 0 obj
<<
/FT /Sig
...
/T (Signature1)
...
>>
endobj
...
/ID [<1952AB9C134E46B58251246E985D5C15> <A57CD5B87222756EC4A096125C7E8A42>]
...
%%EOF
...
35 0 obj
<<
/FT /Sig
...
/T (Signature2)
...
>>
...
%%EOF
This structure is broken, even though it does not give rise to wrong cross references (as the first copy and the second copy are identical except for the ID, the cross references and the startxref offsets point to the respective positions in the first copy). Adobe Reader signature validation can react quite sensitive to such issues.
Thus, you should remove the second copy here to get ahead.
Furthermore, as already mentioned in a comment the SignerInfo of your CMS signature container contains a 1.2.840.113549.1.9.5 signingTime signed attribute. This is forbidden for PAdES BASELINE profiles.
Related
refer my question about prevent adding another approval/signing after lock Add lock Dictionary on Certify Signature PDFBox
i have succeed to certify then lock, but after lock when i try to signing/adding another signature/approval is still valid, what i expected is after lock if another sign the document becomes invalid
rev 1 -> rev 2 -> certified -> lock -> then rev 4 (come and still valid signature)
this what i'm try:
//Create empty signature field
PDDocument docEx=PDDocument.load(inputFile);
PDAcroForm acroFormField = docEx.getDocumentCatalog().getAcroForm();
acroFormField.getCOSObject().setNeedToBeUpdated(true);
COSObject fields = acroFormField.getCOSObject().getCOSObject(COSName.FIELDS);
if (fields != null)
fields.setNeedToBeUpdated(true);
acroFormField.setSignaturesExist(true);
acroFormField.setAppendOnly(true);
acroFormField.getCOSObject().setDirect(true);
PDPage pageField = docEx.getPage(page);
// Create empty signature field, it will get the name "Signature1"
PDSignatureField signatureField = new PDSignatureField(acroFormField);
PDAnnotationWidget widget = signatureField.getWidgets().get(0);
PDRectangle rectField = new PDRectangle((float)humanRect.getX(), (float)humanRect.getY(), (float)humanRect.getWidth(), (float)humanRect.getHeight());
widget.setRectangle(rectField);
widget.getCOSObject().setNeedToBeUpdated(true);
widget.setPage(pageField);
pageField.getAnnotations().add(widget);
pageField.getCOSObject().setNeedToBeUpdated(true);
acroFormField.getFields().add(signatureField);
setLock(signatureField, acroFormField);
docEx.getDocumentCatalog().getCOSObject().setNeedToBeUpdated(true);
docEx.saveIncremental(fos);
fos.close();
//Close and replace with created empty field signature
PDDocument doc = PDDocument.load(signedFile);
FileOutputStream fosSeal = new FileOutputStream(signedFile);
PDSignature signatureLock = new PDSignature();
PDSignatureField signatureFieldLoad = (PDSignatureField) doc.getDocumentCatalog().getAcroForm().getField("Signature1");
LogSystem.info("signatureFieldLoad " + signatureFieldLoad.getValueAsString());
LogSystem.info("signaturelock " + signatureLock);
signatureFieldLoad.setValue(signatureLock);
COSBase lock = signatureFieldLoad.getCOSObject().getDictionaryObject(COS_NAME_LOCK);
if (lock instanceof COSDictionary)
{
COSDictionary lockDict = new COSDictionary();
lockDict.setItem(COS_NAME_ACTION, COS_NAME_ALL);
lockDict.setItem(COSName.TYPE, COS_NAME_SIG_FIELD_LOCK);
COSDictionary transformParams = new COSDictionary(lockDict);
transformParams.setItem(COSName.TYPE, COSName.getPDFName("TransformParams"));
transformParams.setItem(COSName.V, COSName.getPDFName("1.2"));
transformParams.setInt(COSName.P, 1);
transformParams.setDirect(true);
transformParams.setNeedToBeUpdated(true);
COSDictionary sigRef = new COSDictionary();
sigRef.setItem(COSName.TYPE, COSName.getPDFName("SigRef"));
sigRef.setItem(COSName.getPDFName("TransformParams"), transformParams);
sigRef.setItem(COSName.getPDFName("TransformMethod"), COSName.getPDFName("FieldMDP"));
sigRef.setItem(COSName.getPDFName("Data"), doc.getDocumentCatalog());
sigRef.setDirect(true);
COSArray referenceArray = new COSArray();
referenceArray.add(sigRef);
signatureLock.getCOSObject().setItem(COSName.getPDFName("Reference"), referenceArray);
LogSystem.info("LOCK DICTIONARY");
final Predicate<PDField> shallBeLocked;
final COSArray fieldsLock = lockDict.getCOSArray(COSName.FIELDS);
final List<String> fieldNames = fieldsLock == null ? Collections.emptyList() :
fieldsLock.toList().stream().filter(c -> (c instanceof COSString)).map(s -> ((COSString)s).getString()).collect(Collectors.toList());
final COSName action = lockDict.getCOSName(COSName.getPDFName("Action"));
if (action.equals(COSName.getPDFName("Include"))) {
shallBeLocked = f -> fieldNames.contains(f.getFullyQualifiedName());
} else if (action.equals(COSName.getPDFName("Exclude"))) {
shallBeLocked = f -> !fieldNames.contains(f.getFullyQualifiedName());
} else if (action.equals(COSName.getPDFName("All"))) {
shallBeLocked = f -> true;
} else { // unknown action, lock nothing
shallBeLocked = f -> false;
}
lockFields(doc.getDocumentCatalog().getAcroForm().getFields(), shallBeLocked);
setMDPPermission(doc, signatureLock, 2);
// default filter
signatureLock.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
// subfilter for basic and PAdES Part 2 signatures
signatureLock.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signatureLock.setName(name);
// the signing date, needed for valid signature
signatureLock.setSignDate(getDate());
// do not set SignatureInterface instance, if external signing used
SignatureInterface signatureInterface = isExternalSigning() ? null : this;
doc.addSignature(signatureLock, signatureInterface, signatureOptions);
doc.getDocumentCatalog().getAcroForm().getField("Signature1").setPartialName(SignatureField);
if (isExternalSigning()) {
this.tsaUrl=tsaUrl;
ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fosSeal);
// invoke external signature serviceUsing fallback
byte[] cmsSignature =IOUtils.toByteArray(externalSigning.getContent());
String sgn=null;
try {
sgn = signingProcess(cmsSignature);
}catch(Exception e)
{
LogSystem.error(e.toString());
e.printStackTrace();
doc.close();
Telegram tele = new Telegram();
tele.Send(e.toString());
return false;
}
// set signature bytes received from the service
if (sgn != null)
{
externalSigning.setSignature(attachSignature(sgn));
}
}
any suggest ? or anyone can tell me how it work if i want to prevent the document changes ? thank you
I am writing a service that receives PKCS7 data (extracted from signed PDF documents) and needs to verify it.
I am using iText7 PdfPKCS7 for that, but the signature verification always fails. I can read all other information from the PKCS7 (certificates, timestamps etc., I have verified that also with OpenSSL). Only the signature appears as invalid.
Here's the test case:
public static void main(String[] args) throws IOException, GeneralSecurityException,
NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Logger logger = Logger.getLogger(PKCS7Test.class.getName());
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
String path ="/tmp/signed.pdf";
PdfDocument pdf = new PdfDocument(new PdfReader(path));
SignatureUtil signatureUtil = new SignatureUtil(pdf);
List<String> names = signatureUtil.getSignatureNames();
String outerRevisionName = names.get(names.size()-1);
PdfPKCS7 pkcs7In = signatureUtil.verifySignature(outerRevisionName);
boolean isValidSignature = pkcs7In.verify();
logger.log(Level.INFO, "pkcs7In signature is " + ((isValidSignature)?"":"not ") + "valid");
// get hash of original document
Field digestAttrField = PdfPKCS7.class.getDeclaredField("digestAttr");
digestAttrField.setAccessible(true);
byte[] originalDigest = (byte[]) digestAttrField.get(pkcs7In);
// get pkcs7 structure of original signature
PdfDictionary dict = signatureUtil.getSignatureDictionary(outerRevisionName);
PdfString contents = dict.getAsString(PdfName.Contents);
byte [] originalBytes = contents.getValueBytes();
String originalPkcs7 = Base64.getEncoder().encodeToString(originalBytes);
// now reverse process and import PKCS7 data back into object
byte[] pkcs7Bytes = Base64.getDecoder().decode(originalPkcs7);
PdfPKCS7 pkcs7Out = new PdfPKCS7(pkcs7Bytes, PdfName.Adbe_pkcs7_detached, provider.getName());
isValidSignature = pkcs7Out.verify();
logger.log(Level.INFO, "pkcs7Out signature is " + ((isValidSignature)?"":"not ") + "valid");
// get hash of original document from imported signature
digestAttrField = PdfPKCS7.class.getDeclaredField("digestAttr");
digestAttrField.setAccessible(true);
byte [] importedDigest = (byte[]) digestAttrField.get(pkcs7Out);
logger.log(Level.INFO, "Hash values are " + ((Arrays.areEqual(originalDigest, importedDigest))?"":"not ") + "equal");
The output is invariably:
pkcs7In signature is valid
pkcs7Out signature is not valid
Hash values are equal
I suppose I'm doing something wrong with the import, but just can't find out what...
Btw. /tmp/signed.pdf validates OK (signature and content) in other PDF tools (Adobe DC, PdfOnline etc.)
Edit:
I tried to verify the signature with BouncyCastle, which fails too ...
CMSSignedData signature = new CMSSignedData(pkcs7Bytes);
Store certStore = signature.getCertificates();
SignerInformationStore signers = signature.getSignerInfos();
Collection c = signers.getSigners();
Iterator it = c.iterator();
while (it.hasNext())
{
SignerInformation signer = (SignerInformation)it.next();
Collection certCollection = certStore.getMatches(signer.getSID());
Iterator certIt = certCollection.iterator();
X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
try {
signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert));
}
catch (Exception ex) {
logger.log(Level.INFO, "Failed to verify with: " + cert.getSubject().toString() + ": " + ex.getLocalizedMessage());
byte [] contentDigest = signer.getContentDigest();
bi = new BigInteger(1, contentDigest);
String hash = String.format("%0" + (contentDigest.length << 1) + "x", bi);
logger.log(Level.INFO, "Bouncycastle Hash from pkcs7Out is {0}", hash);
}
}
The result is Failed to verify with: ... message-digest attribute value does not match calculated value, and the hash value obviously differs from the real one...
Edit 2:
Workaround by adding separate verify(byte[] msgDigestBytes) method to PdfPKCS7:
public boolean verify(final byte[] msgDigestBytes) throws GeneralSecurityException {
if (verified)
return verifyResult;
if (isTsp) {
TimeStampTokenInfo info = timeStampToken.getTimeStampInfo();
MessageImprint imprint = info.toASN1Structure().getMessageImprint();
byte[] md = msgDigestBytes; // was: messageDigest.digest();
byte[] imphashed = imprint.getHashedMessage();
verifyResult = Arrays.equals(md, imphashed);
} else {
if (sigAttr != null || sigAttrDer != null) {
// was: final byte[] msgDigestBytes = messageDigest.digest();
boolean verifyRSAdata = true;
// Stefan Santesson fixed a bug, keeping the code backward compatible
boolean encContDigestCompare = false;
if (rsaData != null) {
verifyRSAdata = Arrays.equals(msgDigestBytes, rsaData);
encContDigest.update(rsaData);
encContDigestCompare = Arrays.equals(encContDigest.digest(), digestAttr);
}
boolean absentEncContDigestCompare = Arrays.equals(msgDigestBytes, digestAttr);
boolean concludingDigestCompare = absentEncContDigestCompare || encContDigestCompare;
boolean sigVerify = verifySigAttributes(sigAttr) || verifySigAttributes(sigAttrDer);
verifyResult = concludingDigestCompare && sigVerify && verifyRSAdata;
} else {
if (rsaData != null)
sig.update(msgDigestBytes); // was: sig.update(messageDigest.digest());
verifyResult = sig.verify(digest);
}
}
verified = true;
return verifyResult;
}
For this to work, I need the originally signed hash of the document from a trusted source, which in my case I have. Does this still verify that the signature made over the hash is correct?
From
PdfPKCS7 pkcs7Out = new PdfPKCS7(pkcs7Bytes, PdfName.Adbe_pkcs7_detached, provider.getName());
isValidSignature = pkcs7Out.verify();
you cannot expect a proper validation result of the signature: This PdfPKCS7 only knows the CMS signature container, the signature SubFilter, and the security provider to provide algorithm implementations. Thus, it has no information on the very PDF the signature actually is meant to sign. So that piece of code has no means to validate the signature in question, in particular not whether it properly signs its alleged signed data!
If you want to validate the signature using that PdfPKCS7 object, you have to finish initializing it so it does have the required information from the PDF.
To see what is required have a look at the SignatureUtil method verifySignature:
PdfPKCS7 pk = null;
if (sub.equals(PdfName.Adbe_x509_rsa_sha1)) {
PdfString cert = signature.getPdfObject().getAsString(PdfName.Cert);
if (cert == null)
cert = signature.getPdfObject().getAsArray(PdfName.Cert).getAsString(0);
pk = new PdfPKCS7(PdfEncodings.convertToBytes(contents.getValue(), null), cert.getValueBytes(), provider);
}
else
pk = new PdfPKCS7(PdfEncodings.convertToBytes(contents.getValue(), null), sub, provider);
updateByteRange(pk, signature);
PdfString date = signature.getDate();
if (date != null)
pk.setSignDate(PdfDate.decode(date.toString()));
String signName = signature.getName();
pk.setSignName(signName);
String reason = signature.getReason();
if (reason != null)
pk.setReason(reason);
String location = signature.getLocation();
if (location != null)
pk.setLocation(location);
Thus, you have to
update the digest of the signed data like SignatureUtil.updateByteRange does; this is the step that informs the PdfPKCS7 object about the actually signed data to allow actual validation; and
copy several pieces of information from the signature dictionary to the PdfPKCS7 object; for validation purposes in particular the signing time may be of interest.
I want to retrieve the OCSP information from a given X509Certificate object. Since I don't know how to parse this information I am asking this question here.
Here is what I got so far:
X509Certificate x509cert = ... //The Certificate
ASN1Primitive obj = ASN1Primitive.fromByteArray(x509cert
.getExtensionValue(Extension.authorityInfoAccess
.getId()));
AuthorityInformationAccess aia = AuthorityInformationAccess.getInstance(obj);
an exception is thrown:
java.security.cert.CertificateParsingException: java.lang.IllegalArgumentException: unknown object in getInstance: org.bouncycastle.asn1.DEROctetString
How do I parse a ASN1Primitive into a valid DEROctetString to progress?
The value of the ASN1Primitive in the example is:
3032303006082b060105050730018624687474703a2f2f6f6373702e616368656c6f732e64653a383038302f6f6373702f65676b
and seems to me to be a valid value.
You can build a DEROctetString using ASN1InputStream
byte[] authInfoAccessExtensionValue = x509cert.getExtensionValue(X509Extension.authorityInfoAccess.getId());
ASN1InputStream ais1 = new ASN1InputStream(new ByteArrayInputStream(authInfoAccessExtensionValue ));
DEROctetString oct = (DEROctetString) (ais1.readObject());
ASN1InputStream ais2 = new ASN1InputStream(oct.getOctets());
AuthorityInformationAccess authorityInformationAccess = AuthorityInformationAccess.getInstance(ais2.readObject());
Try this code to get the OCSP URI meta-data within a X509Certificate. The code is extracted from class OnlineOCSPSource of SD-DSS project (and slightly modified)
public String getAccessLocation(X509Certificate certificate) throws IOException {
final ASN1ObjectIdentifier ocspAccessMethod = X509ObjectIdentifiers.ocspAccessMethod;
final byte[] authInfoAccessExtensionValue = certificate.getExtensionValue(X509Extension.authorityInfoAccess.getId());
if (null == authInfoAccessExtensionValue) {
return null;
}
ASN1InputStream ais1 = null;
ASN1InputStream ais2 = null;
try {
final ByteArrayInputStream bais = new ByteArrayInputStream(authInfoAccessExtensionValue);
ais1 = new ASN1InputStream(bais);
final DEROctetString oct = (DEROctetString) (ais1.readObject());
ais2 = new ASN1InputStream(oct.getOctets());
final AuthorityInformationAccess authorityInformationAccess = AuthorityInformationAccess.getInstance(ais2.readObject());
final AccessDescription[] accessDescriptions = authorityInformationAccess.getAccessDescriptions();
for (AccessDescription accessDescription : accessDescriptions) {
final boolean correctAccessMethod = accessDescription.getAccessMethod().equals(ocspAccessMethod);
if (!correctAccessMethod) {
continue;
}
final GeneralName gn = accessDescription.getAccessLocation();
if (gn.getTagNo() != GeneralName.uniformResourceIdentifier) {
//Not a uniform resource identifier
continue;
}
final DERIA5String str = (DERIA5String) ((DERTaggedObject) gn.toASN1Primitive()).getObject();
final String accessLocation = str.getString();
return accessLocation;
}
return null;
} finally {
ais1.close();
ais2.close();
}
}
In Bouncy Castle 1.57, there's no need to create the intermediary ASN1Primitive. You can get the extension just by using the org.bouncycastle.asn1.x509.Extension and org.bouncycastle.x509.extension.X509ExtensionUtil classes:
X509Certificate cert = // the certificate
// get Authority Information Access extension
byte[] extVal = cert.getExtensionValue(Extension.authorityInfoAccess.getId());
AuthorityInformationAccess aia = AuthorityInformationAccess.getInstance(X509ExtensionUtil.fromExtensionValue(extVal));
Then you can use the aia object:
AccessDescription[] descriptions = aia.getAccessDescriptions();
for (AccessDescription ad : descriptions) {
// ...
}
You can do it in previous versions as well, but for version <= 1.47 I believe that Extension class doesn't exist and you should use org.bouncycastle.asn1.x509.X509Extension instead (I think that org.bouncycastle.x509.extension.X509ExtensionUtil is the same).
Is there a way to check if an attachment is already present in the PDF document while creating the document (not after the document is saved to disk)? While parsing a XML to PDF I came across over multiple attachments which have the same content (Base64 String from XML > byte[]) and the same name. Currently the attachments are added multiple times, but I want to check if an attachment (with the same content or name) already exists (PdfWriter API?) and if YES, only a new Annotation should be created to the existing attachment.
NOTE: the check should happen while creating the PDF, not with a PdfReader and an existing PDF
EDIT:
Thanks to #Bruno Lowagie I got it working:
protected HashMap<String, PdfFileSpecification> cache = new HashMap<>();
private final byte[] BUFFER = new byte[1024];
public PdfFileSpecification getPdfFileSpecification(final PdfWriter pdfWriter, final String name, final byte[] data) throws IOException {
String hash = createMD5Hash(data);
PdfFileSpecification pdfFileSpecification = cache.get(hash);
if (pdfFileSpecification == null) {
pdfFileSpecification = PdfFileSpecification.fileEmbedded(pdfWriter, null, name, data);
cache.put(hash, pdfFileSpecification);
return pdfFileSpecification;
}
System.out.println(String.format("Name: %s Hash: %s", name, hash));
return pdfFileSpecification;
}
private String createMD5Hash(final byte[] data) {
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
return null;
}
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
try {
int i;
while ((i = byteArrayInputStream.read(BUFFER)) != -1) {
messageDigest.update(BUFFER, 0, i);
}
byteArrayInputStream.close();
} catch (IOException e) {
return null;
}
byte[] mdbytes = messageDigest.digest();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < mdbytes.length; i++) {
sb.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
}
So every time I have to deal with a new attachment I do it like this:
PdfFileSpecification fs = getPdfFileSpecification(pdfWriter, name, data)
PdfAnnotation an = PdfAnnotation.createFileAttachment(pdfWriter, rectangle, name, fs);
Allow me to take your code and introduce some pseudo code to show you how I would do this:
protected Map<String, PdfFileSpecification> cache =
new HashMap<String, PdfFileSpecification>();
public void cellLayout(final PdfPCell pdfPCell, final Rectangle rectangle, final PdfContentByte[] pdfContentBytes) {
String hasheddata = createHash(attachment);
PdfFileSpecification fs = cache.get(hasheddata);
if (fs == null) {
fs = PdfFileSpecification.fileEmbedded(writer, null, displayname, attachment);
cache.put(hasheddata, fs);
}
PdfAnnotation an = PdfAnnotation.createFileAttachment(writer, rectangle, displayname, fs);
writer.addAnnotation(an);
}
This code won't compile because I left out some parts that aren't relevant to the problem. I only kept the stuff that explains the concept of creating the cache for the file specifications.
I create a hash of the attachment bytes to save memory. You will have to implement the createHash() method using the hashing algorithm of your choice. Before I create a new FileSpecification that will write bytes to the PdfWriter, I check if I can't reuse an already existing file specification. If one exists, I reuse it in an annotation. If it doesn't exist I create a new file specification.
I recently got the book "Pro Paypal E-Commerce" by Damon Williams. Its a 2007 copy, so its to be expected that some things, like the code, would change over time.
I'm trying to get this code below to work. I downloaded the paypal_base.jar file and also the paypal_wpstoolkit.jar and put them into my lib folder under jakarta-tomcat (where all my other jars are). I'm having trouble compiling the code.
This code example comes from the book and also http://en.csharp-online.net/Encrypted_Website_Payments%E2%80%94Using_the_PayPal_Java_SDK
I modified it slightly.
import com.paypal.sdk.profiles.EWPProfile;
import com.paypal.sdk.profiles.ProfileFactory;
import com.paypal.wpstoolkit.services.EWPServices;
import com.paypal.sdk.exceptions.PayPalException;
public class PaypalTest {
// path to your PKCS12 file
public static final String PKCS12 = "./Certs/my_pkcs12.p12";
// path to PayPal's public certificate
public static final String PAYPAL_CERT = "./Certs/paypal_cert_pem.txt";
// use https://www.sandbox.paypal.com if testing
//public static final String URL = "https://www.paypal.com";
public static final String URL = "https://sandbox.paypal.com";
public static void main (String args[]) {
// Check to see if the user provided a password
if (args.length != 1) {
System.out.println("You must provide a password.");
System.exit(0);
}
// password used to encrypt your PKCS12 files
// obtained from the command line
String USER_PASSWORD = args[0];
// First we will create the EWPProfile object
try {
com.paypal.sdk.profiles.EWPProfile ewpProfile = ProfileFactory.createEWPProfile();
ewpProfile.setCertificateFile(PKCS12);
ewpProfile.setPayPalCertificateFile(PAYPAL_CERT);
ewpProfile.setPrivateKeyPassword(USER_PASSWORD);
ewpProfile.setUrl(URL);
String buttonParameters = "cmd=_xclick\nbusiness=buyer#hotmail.com\nitem_name=vase\nitemprice=25.00";
// Next we will create the EWPServices object
// and tell it which EWPProfile object to use
EWPServices ewpServices = new EWPServices();
ewpServices.setEWPProfile(ewpProfile);
// Finally we are ready to call the method to perform the button encryption
String encryptedButton = ewpServices.encryptButton(buttonParameters.getBytes());
System.out.println(encryptedButton);
} catch (PayPalException ppe) {
System.out.println("An exception occurred when creating the button.");
ppe.printStackTrace();
}
}
}//class
The errors I'm getting during compilation are as follows -
java:51: cannot find symbol
symbol: method setEWPProfile(com.paypal.sdk.profiles.EWPProfile)
location: class com.paypal.wpstoolkit.services.EWPServices
ewpServices.setEWPProfile(ewpProfile);
java:55: encryptButton(byte[],java.lang.String,java.lang.String.,java.lang.String.,java.lang.String) in com.paypal.wpstoolkit.services.EWPServices cannot be applied to (byte[])
ewpServices.encryptButton(buttonParameters.getBytes());
The paypal_base jar only has NVPCallerServices.class in it, and not EWPServices. EWPServices is in the wpstoolkit jar.
How do I fix my errors? I'm having trouble finding documentation on the paypal classes.
The updated Java SDK + API documenatation can be found here:
https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_Java_NVP_SDK.zip
Extract that .zip and open docs/index.html
That is where you can find all of the API documentation. It looks like you are trying to make calls to methods that no longer exist. Have a look through the new classes and see what will work for you.
Looks like with the newer API's Paypal want you to have all button code generated from their web service, as they seem to have removed the EWPService class from the SDK. But then I noticed they still provide the client side utility with with you can manually generate the code here. After a little tweaking, I got the code in there to do what I needed (encrypt an upload cart button locally).
Assuming you use Java 5+, just make sure you this and this in your classpath. Now the code isn't perfect, as it contains a bunch of deprecated methods, but for such a trivial task as encrypting the button code, it works just fine.
public String getButtonEncryptionValue(String _data,
String _privateKeyPath, String _certPath, String _payPalCertPath,
String _keyPass) throws IOException, CertificateException,
KeyStoreException, UnrecoverableKeyException,
InvalidAlgorithmParameterException, NoSuchAlgorithmException,
NoSuchProviderException, CertStoreException, CMSException {
_data = _data.replace(',', '\n');
CertificateFactory cf = CertificateFactory.getInstance("X509", "BC");
// Read the Private Key
KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
ks.load(new FileInputStream(_privateKeyPath), _keyPass.toCharArray());
String keyAlias = null;
Enumeration<String> aliases = ks.aliases();
while (aliases.hasMoreElements()) {
keyAlias = aliases.nextElement();
}
PrivateKey privateKey = (PrivateKey) ks.getKey(keyAlias,
_keyPass.toCharArray());
// Read the Certificate
X509Certificate certificate = (X509Certificate) cf
.generateCertificate(new FileInputStream(_certPath));
// Read the PayPal Cert
X509Certificate payPalCert = (X509Certificate) cf
.generateCertificate(new FileInputStream(_payPalCertPath));
// Create the Data
byte[] data = _data.getBytes();
// Sign the Data with my signing only key pair
CMSSignedDataGenerator signedGenerator = new CMSSignedDataGenerator();
signedGenerator.addSigner(privateKey, certificate,
CMSSignedDataGenerator.DIGEST_SHA1);
ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
certList.add(certificate);
CertStore certStore = CertStore.getInstance("Collection",
new CollectionCertStoreParameters(certList));
signedGenerator.addCertificatesAndCRLs(certStore);
CMSProcessableByteArray cmsByteArray = new CMSProcessableByteArray(data);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
cmsByteArray.write(baos);
System.out.println("CMSProcessableByteArray contains ["
+ baos.toString() + "]");
CMSSignedData signedData = signedGenerator.generate(cmsByteArray, true,
"BC");
byte[] signed = signedData.getEncoded();
CMSEnvelopedDataGenerator envGenerator = new CMSEnvelopedDataGenerator();
envGenerator.addKeyTransRecipient(payPalCert);
CMSEnvelopedData envData = envGenerator.generate(
new CMSProcessableByteArray(signed),
CMSEnvelopedDataGenerator.DES_EDE3_CBC, "BC");
byte[] pkcs7Bytes = envData.getEncoded();
return new String(DERtoPEM(pkcs7Bytes, "PKCS7"));
}
public static byte[] DERtoPEM(byte[] bytes, String headfoot) {
ByteArrayOutputStream pemStream = new ByteArrayOutputStream();
PrintWriter writer = new PrintWriter(pemStream);
byte[] stringBytes = Base64.encode(bytes);
System.out.println("Converting " + stringBytes.length + " bytes");
String encoded = new String(stringBytes);
if (headfoot != null) {
writer.print("-----BEGIN " + headfoot + "-----\n");
}
// write 64 chars per line till done
int i = 0;
while ((i + 1) * 64 < encoded.length()) {
writer.print(encoded.substring(i * 64, (i + 1) * 64));
writer.print("\n");
i++;
}
if (encoded.length() % 64 != 0) {
writer.print(encoded.substring(i * 64)); // write remainder
writer.print("\n");
}
if (headfoot != null) {
writer.print("-----END " + headfoot + "-----\n");
}
writer.flush();
return pemStream.toByteArray();
}
An easier way to do this is not to encrypt, but use an unencrypted button and then a hash trick to detect tampering. I explain this here with PHP, but you can translate to Java.
How do I make a PayPal encrypted buy now button with custom fields?