How to parse AuthoritiyInformation from X509Certificate object - java

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).

Related

PAdES Signature Level - Adobe Acrobat

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.

Bouncy Castle - Get Hash before sign from TimeStampResponse

I am getting signed hash from TSA by using Bouncy Castle like this-
TimeStampResponse GetSignedHashFromTsa(byte[] hash)
{
TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
TimeStampRequest request = reqGen.Generate(
TspAlgorithms.Sha1,
hash,
BigInteger.ValueOf(100)
);
byte[] reqData = request.GetEncoded();
HttpWebRequest httpReq = (HttpWebRequest)WebRequest.Create("http://www.cryptopro.ru/tsp/tsp.srf");
httpReq.Method = "POST";
httpReq.ContentType = "application/timestamp-query";
httpReq.ContentLength = reqData.Length;
// Write the request content
Stream reqStream = httpReq.GetRequestStream();
reqStream.Write(reqData, 0, reqData.Length);
reqStream.Close();
HttpWebResponse httpResp = (HttpWebResponse)httpReq.GetResponse();
// Read the response
Stream respStream = new BufferedStream(httpResp.GetResponseStream());
TimeStampResponse response = new TimeStampResponse(respStream);
respStream.Close();
return response;
}
From this function, I can get a TimeStampResponse object (same in Java and C#) from a byte[].
I like to get the byte[] from the TimeStampResponse object in another class. Is there any way?
Thanks in advance for helping.
Re-
For a better understanding of Sai Ye Yan Naing Aye, I am calling the function like this-
byte[] hashToSign = ....;
TimeStampResponse response = GetSignedHashFromTsa(hashToSign);
byte[] signedByteToSaveInFile = response.GetEncoded();
Then I am saving signedByteToSaveInFile in a file. Later I am trying to find the byte[] what is signed. Say, I am doing this-
byte[] signedByteToSaveInFile = ....; //Read byte array from file
TimeStampResponse previouslyTsaSignedDataResponse = new TimeStampResponse(signedByteToSaveInFile);
Now I like to get the byte array what was sent to TSA server before sign from previouslyTsaSignedDataResponse object. So, I like to get byte[] hash what was sent to TSA server to sign. In another word, I like to get the main content before sign.
Think, now the question is more clear.
I have solved it myself like this-
bool ValidateTimestamp(TimeStampResponse tr, byte[] hash)
{
try
{
TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
TimeStampRequest request = reqGen.Generate(
TspAlgorithms.Sha1,
hash,
BigInteger.ValueOf(100)
);
tr.Validate(request);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
return false;
}
return tr.GetFailInfo() == null;
}

Does Apache POI support signing the word twice?

I tried to run the signature code twice , but I found that it just geneneral sig1.xml , not sig1.xml and sig2.xml .
char password[] = "test".toCharArray();
File file = new File("test.pfx");
KeyStore keystore = KeyStore.getInstance("PKCS12");
FileInputStream fis = new FileInputStream(file);
keystore.load(fis, password);
fis.close();
// extracting private key and certificate
String alias = "xyz"; // alias of the keystore entry
Key key = keystore.getKey(alias, password);
X509Certificate x509 = (X509Certificate)keystore.getCertificate(alias);
// filling the SignatureConfig entries (minimum fields, more options are available ...)
SignatureConfig signatureConfig = new SignatureConfig();
signatureConfig.setKey(keyPair.getPrivate());
signatureConfig.setSigningCertificateChain(Collections.singletonList(x509));
OPCPackage pkg = OPCPackage.open(..., PackageAccess.READ_WRITE);
signatureConfig.setOpcPackage(pkg);
// adding the signature document to the package
SignatureInfo si = new SignatureInfo();
si.setSignatureConfig(signatureConfig);
si.confirmSignature();
Inside org.apache.poi.poifs.crypt.dsig.SignatureInfo there is a method writeDocument(Document document) that creates the sig1.xml overriding the previous one (if any), that's why if you sign it multiple times you will only see the last signature, altough inside the _xmlsignatures/_rels/origin.sigs.rels of the docx/xlsx you can see a relation to each signature.
if you replace:
sigPartName = PackagingURIHelper.createPartName("/_xmlsignatures/sig1.xml");
with something like:
SignatureInfo si = new SignatureInfo();
si.setSignatureConfig(signatureConfig);
Iterator<?> iterator = si.getSignatureParts().iterator();
int i = 1;
while (iterator.hasNext()) {
iterator.next();
i++;
}
sigPartName = PackagingURIHelper.createPartName("/_xmlsignatures/sig" + i + ".xml");
it will create sig1.xml, sig2.xml, ... and when opening docx/xlsx all signatures will be there.
I've fixed the original issue via #63011, which will be released in POI 4.1.0.
The relevant part of the ticket is.
There's a new config property in SignatureConfig.setAllowMultipleSignatures(true) to allow adding of signatures, which is by default false to stay backward compatible.

iText7: How to verify signature in detached PKCS7?

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.

Paypal Java SDK - trying to create encrypted button - doesn't work, can't find documentation

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?

Categories