Hi, My name is Miklos Kertesz, I am new in this forum. What I'd like to have, it's a Time Stamp example in a PDF file with digital signature. I generate PDF-s(resp. conert from other file format /afp/ in C++, not in java, but a PDF example with TimeStamp would help me a lot. What I don't see as well, how it should communicate -during PDF generation- with the Time Stamp server(TSA): /http request from PDF: hash sent to TSA, modifyied hash got back, ok, but how should I save in the PDF file, e.g. subdictionary of digital signature annotation with a stream or similar/ Any help is welcome
Obrigado muito Miklos Paulo Soares wrote: > > It's better to post the diffs as attachments. I attach a contribution on > the same subject by Xavier Le Vourch. > > Paulo > >> -----Original Message----- >> From: [EMAIL PROTECTED] >> [mailto:[EMAIL PROTECTED] On >> Behalf Of Martin Brunecky >> Sent: Wednesday, July 25, 2007 7:14 PM >> To: [email protected] >> Subject: [iText-questions] PDF Digital signature with >> timestamp - using TimeStamp Authority - example >> >> Since iText currently lacks a direct support for time-stamped >> PDF signatures >> >> (signatures backed by a Time Stamp Authority - TSA), a fair >> amount of effort >> >> is needed to match the Acrobat and Reader functionality. >> >> However, it can be done - at the cost of iText source >> (PdfPKCS7) augmentation. >> >> >> >> The following posting is essentially a re-packaging of the >> original posting by >> >> >> >> >>>>>> Aiken Sam, 2006-11-15 <<<<<<<< >> >> >> >> Sam deserves all the credits for this functionality. My >> contribution is limited >> >> to refactoring for subclassing, some minor fixes, cleanup and testing. >> >> >> >> OBJECTIVE: >> >> My posting is made out of self-interest: I would like to see >> the support for >> >> time-stamped signing built into iText. Because class PdfPKCS7 >> was not meant >> >> for subclassing, any "outside iText" implementation ends up >> cloning most >> >> of that class - even though changes required for >> time-stamping are small. >> >> >> >> My re-packaging of Aiken Sam's work aimed at two main goals: >> >> - embed the support within iText with minimal changes to the >> existing code >> >> - allow subclassing the TSA client for specific RFC 3161 >> timestamp providers >> >> (such as using provider supplied toolkit) and communication >> protocols. >> >> >> >> TESTING: >> >> I have done testing using several flavors of certificate >> (PKCS12 based certs, >> >> PKCS11 USB token from VeriSign CDS for Adobe). >> >> I tested using TSA at >> "http://dse200.ncipher.com/TSS/HttpTspServer" (no account >> >> or password needed) and TSA at www.digistamp.com, offering >> 'test' service for free. >> >> Other services used by Aiken Sam in his testing are now off-the-air. >> >> I also tested with other Bouncy Castle releases (bc*-jdk15-137.jar). >> >> >> >> DEPENDENCIES: >> >> The class TSAClientBouncyCastle adds a new iText build >> dependency, Bouncy Castle >> >> time stamp support: bctsp-jdk14-135.jar (you must add this to >> src/ant/compile.xml). >> >> You can find this jar on sourceforge, or at www.bouncycastle.org. >> >> >> >> >> >> DEMO: >> >> Included (below) is a 'demo' sample generating an invisible, >> time-stamped >> >> signature, with its support classes. All code builds with >> javac -source=1.4, >> >> even though I prefer 1.5. >> >> >> >> LIMITATIONS: >> >> The implementation and demo provided here uses explicit calls >> to preClose() and >> >> PdfPKCS7.getEncodedPKCS7(). >> >> It would be possible to 'hide' all of this by adding something like >> >> sap.setTSAClient(client) together with adding time-stamp >> handling to preClose() >> >> and close() methods -- I considered such changes too >> intrusive at this point. >> >> >> >> ISSUES: >> >> The main issue is what exactly Adobe considers a valid >> timestamp. The RFC 3161 >> >> leaves the time stamped "imprint" open to the protocol users >> (only asserting the >> >> TSA must NOT change it), and hence it is upon the application >> to define it. >> >> When Adobe reader encounters some 'other' time-stamped >> imprint, it ignores it. >> >> I would appreciate any pointers to Adobe specs, as I would >> like to expand upon >> >> the current work - in some cases, my control over the >> 'imprint' generation >> >> is limited. >> >> >> >> >> >> >> >> ===== iText SOURCE CHANGES >> ===================================================== >> >> Source changes comprise of changes to PdfPKCS7 (3 areas), one >> new interface >> >> and one new class ("default" TSA caller implementation). >> >> >> >> ===== PdfPKCS7.java >> ============================================================ >> >> The following is a diff output showing changes from iText >> 2.0.4 source. Changes >> >> include one signature change (getEncodedPKCS7), adding >> backwards-compatible ones, >> >> TSA client calling code, and one new method - >> buildUnauthenticatedAttributes(). >> >> >> >> >diff -b PdfPKCS7.orig PdfPKCS7.java >> >> 770c770 >> >> < return getEncodedPKCS7(null, null); >> >> --- >> >> > return getEncodedPKCS7(null, null, null); >> >> 780a781,793 >> >> > return getEncodedPKCS7(secondDigest, signingTime, null); >> >> > } >> >> > >> >> > /** >> >> > * Gets the bytes for the PKCS7SignedData object. >> Optionally the authenticatedAttributes >> >> > * in the signerInfo can also be set, OR a >> time-stamp-authority client >> >> > * may be provided. >> >> > * @param secondDigest the digest in the authenticatedAttributes >> >> > * @param signingTime the signing time in the >> authenticatedAttributes >> >> > * @param tsaClient TSAClient - null or an optional >> time stamp authority client >> >> > * @return byte[] the bytes for the PKCS7SignedData object >> >> > */ >> >> > public byte[] getEncodedPKCS7(byte secondDigest[], >> Calendar signingTime, TSAClient tsaClient) { >> >> 881a895,907 >> >> > // When requested, go get and add the >> timestamp. May throw an exception. >> >> > // Added by Martin Brunecky, 07/12/2007 >> folowing Aiken Sam, 2006-11-15 >> >> > // Sam found Adobe expects time-stamped SHA1-1 >> of the encrypted digest >> >> > if (tsaClient != null) { >> >> > byte[] tsImprint = >> MessageDigest.getInstance("SHA-1").digest(digest); >> >> > byte[] tsToken = >> tsaClient.getTimeStampToken(this, tsImprint); >> >> > if (tsToken != null) { >> >> > ASN1EncodableVector unauthAttributes = >> buildUnauthenticatedAttributes(tsToken); >> >> > if (unauthAttributes != null) { >> >> > signerinfo.add(new >> DERTaggedObject(false, 1, new DERSet(unauthAttributes))); >> >> > } >> >> > } >> >> > } >> >> 922a949,976 >> >> > /** >> >> > * Added by Aiken Sam, 2006-11-15, modifed by Martin >> Brunecky 07/12/2007 >> >> > * to start with the timeStampToken (signedData >> 1.2.840.113549.1.7.2). >> >> > * Token is the TSA response without response status, >> which is usually >> >> > * handled by the (vendor supplied) TSA >> request/response interface). >> >> > * @param timeStampToken byte[] - time stamp token, DER >> encoded signedData >> >> > * @return ASN1EncodableVector >> >> > * @throws IOException >> >> > */ >> >> > private ASN1EncodableVector >> buildUnauthenticatedAttributes(byte[] timeStampToken) throws >> IOException { >> >> > if (timeStampToken == null) >> >> > return null; >> >> > >> >> > // @todo: move this together with the rest of the defintions >> >> > String ID_TIME_STAMP_TOKEN = >> "1.2.840.113549.1.9.16.2.14"; // RFC 3161 id-aa-timeStampToken >> >> > >> >> > ASN1InputStream tempstream = new >> ASN1InputStream(new ByteArrayInputStream(timeStampToken)); >> >> > ASN1EncodableVector unauthAttributes = new >> ASN1EncodableVector(); >> >> > >> >> > ASN1EncodableVector v = new ASN1EncodableVector(); >> >> > v.add(new >> DERObjectIdentifier(ID_TIME_STAMP_TOKEN)); // id-aa-timeStampToken >> >> > ASN1Sequence seq = (ASN1Sequence) tempstream.readObject(); >> >> > v.add(new DERSet(seq)); >> >> > >> >> > unauthAttributes.add(new DERSequence(v)); >> >> > return unauthAttributes; >> >> > } >> >> > >> >> >> >> ===== TSAClient.java >> =u========================================================= >> >> package com.lowagie.text.pdf; >> >> >> >> /** >> >> * Time Stamp Authority client (caller) interface. >> >> * <p> >> >> * Interface used by the PdfPKCS7 digital signature builder to call >> >> * Time Stamp Authority providing RFC 3161 compliant time stamp token. >> >> * @author Martin Brunecky, 07/17/2007 >> >> */ >> >> public interface TSAClient { >> >> /** >> >> * Get the time stamp token size estimate. >> >> * Implementation must return value large enough to >> accomodate the entire token >> >> * returned by getTimeStampToken() _prior_ to actual >> getTimeStampToken() call. >> >> */ >> >> public int getTokenSizeEstimate(); >> >> >> >> /** >> >> * Get RFC 3161 timeStampToken. >> >> * Method may return null indicating that timestamp >> should be skipped. >> >> * @param caller PdfPKCS7 - calling PdfPKCS7 instance (in >> case caller needs it) >> >> * @param imprint byte[] - data imprint to be time-stamped >> >> * @return byte[] - encoded, TSA signed data of the timeStampToken >> >> * @throws Exception - TSA request failed >> >> */ >> >> public byte[] getTimeStampToken(PdfPKCS7 caller, byte[] >> imprint) throws Exception; >> >> >> >> } >> >> >> >> >> >> ===== TSAClientBouncyCastle.java >> =============================================== >> >> package com.lowagie.text.pdf; >> >> >> >> import java.io.*; >> >> import java.math.*; >> >> import java.net.*; >> >> >> >> >> >> import org.bouncycastle.asn1.cmp.*; >> >> import org.bouncycastle.asn1.x509.*; >> >> import org.bouncycastle.tsp.*; >> >> >> >> /** >> >> * Time Stamp Authority Client interface implementation using >> Bouncy Castle >> >> * org.bouncycastle.tsp package. >> >> * <p> >> >> * Created by Aiken Sam, 2006-11-15, refactored by Martin >> Brunecky, 07/15/2007 >> >> * for ease of subclassing. >> >> * </p> >> >> */ >> >> public class TSAClientBouncyCastle implements TSAClient { >> >> protected String tsaURL; >> >> protected String tsaUsername; >> >> protected String tsaPassword; >> >> protected int tokSzEstimate; >> >> >> >> public TSAClientBouncyCastle(String url) { >> >> this(url, null, null, 4096); >> >> } >> >> >> >> public TSAClientBouncyCastle(String url, String username, >> String password) { >> >> this(url, username, password, 4096); >> >> } >> >> >> >> /** >> >> * Constructor. >> >> * Note the token size estimate is updated by each call, >> as the token >> >> * size is not likely to change (as long as we call the >> same TSA using >> >> * the same imprint length). >> >> * @param url String - Time Stamp Authority URL (i.e. >> "http://tsatest1.digistamp.com/TSA") >> >> * @param username String - user(account) name >> >> * @param password String - password >> >> * @param tokSzEstimate int - estimated size of received >> time stamp token (DER encoded) >> >> */ >> >> public TSAClientBouncyCastle(String url, String username, >> String password, int tokSzEstimate) { >> >> this.tsaURL = url; >> >> this.tsaUsername = username; >> >> this.tsaPassword = password; >> >> this.tokSzEstimate = tokSzEstimate; >> >> } >> >> >> >> /** >> >> * Get the token size estimate. >> >> * Returned value reflects the result of the last >> succesfull call, padded >> >> * @return int >> >> */ >> >> public int getTokenSizeEstimate() { >> >> return tokSzEstimate; >> >> } >> >> >> >> public byte[] getTimeStampToken(PdfPKCS7 caller, byte[] >> imprint) throws Exception { >> >> return getTimeStampToken(imprint); >> >> } >> >> >> >> /** >> >> * Get timestamp token - Bouncy Castle request encoding / >> decoding layer >> >> */ >> >> protected byte[] getTimeStampToken(byte[] imprint) throws >> Exception { >> >> byte[] respBytes = null; >> >> try { >> >> // Setup the time stamp request >> >> TimeStampRequestGenerator tsqGenerator = new >> TimeStampRequestGenerator(); >> >> tsqGenerator.setCertReq(true); >> >> // tsqGenerator.setReqPolicy("1.3.6.1.4.1.601.10.3.1"); >> >> BigInteger nonce = >> BigInteger.valueOf(System.currentTimeMillis()); >> >> TimeStampRequest request = >> tsqGenerator.generate(X509ObjectIdentifiers.id_SHA1.getId() , >> imprint, nonce); >> >> byte[] requestBytes = request.getEncoded(); >> >> >> >> // Call the communications layer >> >> respBytes = getTSAResponse(requestBytes); >> >> >> >> // Handle the TSA response >> >> TimeStampResponse response = new >> TimeStampResponse(respBytes); >> >> >> >> // validate communication level attributes (RFC >> 3161 PKIStatus) >> >> response.validate(request); >> >> PKIFailureInfo failure = response.getFailInfo(); >> >> int value = (failure == null) ? 0 : failure.intValue(); >> >> if (value != 0) { >> >> // @todo: Translate value of 15 error codes >> defined by PKIFailureInfo to string >> >> throw new Exception("Invalid TSA '" + tsaURL + >> "' response, code " + value); >> >> } >> >> // @todo: validate the time stap certificate chain >> (if we want >> >> // assure we do not sign using an invalid >> timestamp). >> >> >> >> // extract just the time stamp token (removes >> communication status info) >> >> TimeStampToken tsToken = response.getTimeStampToken(); >> >> if (tsToken == null) { >> >> throw new Exception("TSA '" + tsaURL + "' >> failed to return time stamp token"); >> >> } >> >> TimeStampTokenInfo info = >> tsToken.getTimeStampInfo(); // to view details >> >> byte[] encoded = tsToken.getEncoded(); >> >> long stop = System.currentTimeMillis(); >> >> >> >> // Update our token size estimate for the next >> call (padded to be safe) >> >> this.tokSzEstimate = encoded.length + 32; >> >> return encoded; >> >> } >> >> catch (Exception e) { >> >> throw e; >> >> } >> >> catch (Throwable t) { >> >> throw new Exception("Failed to get TSA response >> from '" + tsaURL +"'", t); >> >> } >> >> } >> >> >> >> /** >> >> * Get timestamp token - communications layer >> >> * @return - byte[] - TSA response, raw bytes (RFC 3161 encoded) >> >> */ >> >> protected byte[] getTSAResponse(byte[] requestBytes) >> throws Exception { >> >> // Setup the TSA connection >> >> URL url = new URL(tsaURL); >> >> URLConnection tsaConnection = (URLConnection) >> url.openConnection(); >> >> >> >> tsaConnection.setDoInput(true); >> >> tsaConnection.setDoOutput(true); >> >> tsaConnection.setUseCaches(false); >> >> tsaConnection.setRequestProperty("Content-Type", >> "application/timestamp-query"); >> >> >> //tsaConnection.setRequestProperty("Content-Transfer-Encoding" >> , "base64"); >> >> >> tsaConnection.setRequestProperty("Content-Transfer-Encoding", >> "binary"); >> >> >> >> if ((tsaUsername != null) && !tsaUsername.equals("") ) { >> >> String userPassword = tsaUsername + ":" + tsaPassword; >> >> tsaConnection.setRequestProperty("Authorization", >> "Basic " + >> >> new String(new >> sun.misc.BASE64Encoder().encode(userPassword.getBytes()))); >> >> }; >> >> OutputStream out = tsaConnection.getOutputStream(); >> >> out.write(requestBytes); >> >> out.close(); >> >> >> >> // Get TSA response as a byte array >> >> InputStream inp = tsaConnection.getInputStream(); >> >> ByteArrayOutputStream baos = new ByteArrayOutputStream(); >> >> byte[] buffer = new byte[1024]; >> >> int bytesRead = 0; >> >> while ((bytesRead = inp.read(buffer, 0, >> buffer.length)) >= 0) { >> >> baos.write(buffer, 0, bytesRead); >> >> } >> >> byte[] respBytes = baos.toByteArray(); >> >> >> >> String encoding = tsaConnection.getContentEncoding(); >> >> if (encoding != null && encoding.equalsIgnoreCase("base64")) { >> >> sun.misc.BASE64Decoder dec = new sun.misc.BASE64Decoder(); >> >> respBytes = dec.decodeBuffer(new String(respBytes)); >> >> } >> >> return respBytes; >> >> } >> >> >> >> } >> >> >> >> >> >> ===== D E M O >> ================================================================== >> >> To use the following demo, you must have a PKCS12 >> certificate, such as VeriSign >> >> digital mail ID (or refer to my PKCS11 posting for PKCS11, >> JCS cert support), >> >> and you need access to some Time Stamp Authority (TSA) >> service, such as an >> >> account with www.digistamp.com (free test service available). >> >> >> >> Depending upon certicate(s) used, you may have to adjust >> trusted certificates >> >> in your Adobe Acrobat or reader. >> >> >> >> ===== PdfSignerDemo.java >> ======================================================= >> >> >> >> package demo; >> >> >> >> import java.io.*; >> >> import java.util.*; >> >> >> >> import com.lowagie.text.*; >> >> import com.lowagie.text.pdf.*; >> >> >> >> /** >> >> * Demo using iText to digitally sign PDF document with a >> valid time-stamp >> >> * Demo dependecies: >> >> * SignerKeystore - interface providing signing certificate access >> >> * SignerKeystorePKCS12 - implemnation importing PKCS12 >> (.pfx) certificate >> >> */ >> >> public class PdfSignerDemo { >> >> private SignerKeystore sks; >> >> private TSAClient def; >> >> >> >> public PdfSignerDemo(SignerKeystore sks, TSAClient tsc) >> throws Exception { >> >> this.sks = sks; >> >> this.def = tsc; >> >> } >> >> >> >> public void signPDF(String srcFile, String dstFile) >> throws Exception { >> >> signPDF(srcFile, dstFile, def); >> >> } >> >> >> >> public void signPDF(String srcFile, String dstFile, >> TSAClient tsc) { >> >> try { >> >> // Prepare for PDF file handling (copy, stamp) >> >> PdfReader reader = new PdfReader(srcFile); >> >> FileOutputStream fout = new FileOutputStream(dstFile); >> >> PdfStamper stp = >> PdfStamper.createSignature(reader, fout, '\0'); >> >> PdfSignatureAppearance sap = stp.getSignatureAppearance(); >> >> setAppearance(sap); >> >> >> >> // Configure PDF signature dictionary >> (PdfName.ADOBE_PPKLITE works too) >> >> PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKMS, >> >> >> PdfName.ADBE_PKCS7_SHA1); >> >> // dic.setName (null); // optional - PdfPKCS7 >> uses the certificate CN >> >> dic.setReason(sap.getReason()); >> >> dic.setLocation(sap.getLocation()); >> >> dic.setContact(sap.getContact()); >> >> dic.setDate(new PdfDate(sap.getSignDate())); // >> time-stamp will over-rule this >> >> sap.setCryptoDictionary(dic); >> >> >> >> // Estimate signature size, creating a 'fake' one >> using fake data >> >> // (SHA1 length does not depend upon the data length) >> >> byte[] estSignature = genPKCS7Signature(new >> ByteArrayInputStream("fake".getBytes()), null); >> >> int contentEst = estSignature.length + >> >> ((tsc == null) ? 0 : >> tsc.getTokenSizeEstimate()); >> >> >> >> // Preallocate excluded byte-range for the >> signature content (hex encoded) >> >> HashMap exc = new HashMap(); >> >> exc.put(PdfName.CONTENTS, new Integer(contentEst >> * 2 + 2)); >> >> sap.preClose(exc); >> >> >> >> // Get the true data signature, including a true >> time stamp token >> >> byte[] encodedSig = >> genPKCS7Signature(sap.getRangeStream(), tsc); >> >> if (contentEst + 2 < encodedSig.length) { >> >> throw new Exception("Timestamp size estimate >> " + contentEst + >> >> " is too low for actual " + >> >> encodedSig.length); >> >> } >> >> >> >> // Copy signature into a zero-filled array, >> padding it up to estimate >> >> byte[] paddedSig = new byte[contentEst]; >> >> System.arraycopy(encodedSig, 0, paddedSig, 0, >> encodedSig.length); >> >> > >> >> // Finally, load zero-padded signature into the >> signature field /Content >> >> PdfDictionary dic2 = new PdfDictionary(); >> >> dic2.put(PdfName.CONTENTS, new >> PdfString(paddedSig).setHexWriting(true)); >> >> sap.close(dic2); // closes sap.close() >> >> } catch (Throwable t) { >> >> System.out.println("Signing failed" + t); >> >> t.printStackTrace(); >> >> } >> >> } >> >> >> >> >> >> /** >> >> * Setup signature appearance. Override to define specifics. >> >> * @param sap PdfSignatureAppearance >> >> */ >> >> protected void setAppearance(PdfSignatureAppearance sap) { >> >> // Make this an invisible signature >> >> sap.setVisibleSignature(new Rectangle(0, 0, 0, 0), 1, >> "Signature"); // empty makes field invisible >> >> } >> >> >> >> >> >> /** >> >> * Generate the PKCS7 encoded signature >> >> * @param data InputStream - data to digest >> >> * @param doTimestamp boolean - true to include time-stamp >> >> * @return byte[] >> >> * @throws Exception >> >> */ >> >> protected byte[] genPKCS7Signature(InputStream data, >> TSAClient tsc) throws Exception { >> >> // assume sub-filter is adobe.pkcs7.sha1 >> >> PdfPKCS7 sgn = new PdfPKCS7(sks.getPrivateKey(), >> sks.getChain(), null, >> >> "SHA1", >> sks.getProvider().getName(), true); >> >> byte[] buff = new byte[2048]; >> >> int len = 0; >> >> while ((len = data.read(buff)) > 0) { >> >> sgn.update(buff, 0, len); >> >> } >> >> return sgn.getEncodedPKCS7(null, null, tsc); >> >> } >> >> >> >> // Configuration >> >> // MY digital certificate (PKCS#12 - get an e-mail >> digital ID from VeriSign) >> >> private static final String CERT_PATH = "mycert.pfx"; >> >> private static final String CERT_PASSW = "mypassword"; >> >> >> >> // MY TSA account (go to www.digistamp.com and create one >> - test is free) >> >> private static final String TSA_URL = >> "http://tsatest1.digistamp.com/TSA" ; >> >> private static final String TSA_ACCNT = "99999"; >> >> private static final String TSA_PASSW = "pwdpwd"; >> >> >> >> public static void main(String[] args) throws Exception { >> >> if (args.length < 1) { >> >> System.out.println("Usage: PdfSignerDemo file >> {file} {file}..."); >> >> System.exit(1); >> >> } >> >> SignerKeystore sks = new SignerKeystorePKCS12(new >> FileInputStream(CERT_PATH), CERT_PASSW); >> >> TSAClient tsc = new >> TSAClientBouncyCastle(TSA_URL, TSA_ACCNT, TSA_PASSW); >> >> >> >> PdfSignerDemo demo = new PdfSignerDemo(sks, tsc); >> >> for (int i=0; i<args.length; i++) { >> >> int iDot = args[i].lastIndexOf('.'); >> >> demo.signPDF(args[i], args[i].substring(0, iDot) >> + "_signed" + args[i].substring(iDot)); >> >> } >> >> } >> >> } >> >> >> >> ===== SignerKeystore.java >> ====================================================== >> >> package demo; >> >> >> >> import java.security.Provider; >> >> import java.security.PrivateKey; >> >> import java.security.cert.Certificate; >> >> >> >> public interface SignerKeystore { >> >> public PrivateKey getPrivateKey() ; >> >> public Certificate[] getChain() ; >> >> public Provider getProvider(); >> >> } >> >> >> >> >> >> ===== SignerKeystorePKCS12.java >> ================================================ >> >> package demo; >> >> >> >> import java.io.*; >> >> import java.security.*; >> >> import java.security.cert.Certificate; >> >> >> >> /** >> >> * SignerKeystore implementation using PKCS#12 file (.pfx etc) >> >> */ >> >> public class SignerKeystorePKCS12 implements SignerKeystore { >> >> private static Provider prov = null; >> >> private KeyStore ks; >> >> private String alias; >> >> private String pwd; >> >> >> >> private PrivateKey key; >> >> private Certificate[] chain; >> >> >> >> public SignerKeystorePKCS12(InputStream inp, String >> passw) throws Exception { >> >> // This should be done once only for the provider... >> >> if (prov == null) { >> >> prov = new >> org.bouncycastle.jce.provider.BouncyCastleProvider(); >> >> Security.addProvider(prov); >> >> } >> >> >> >> this.ks = KeyStore.getInstance("pkcs12", prov); >> >> this.pwd = passw; >> >> this.ks.load(inp, pwd.toCharArray()); >> >> this.alias = (String)ks.aliases().nextElement(); >> >> this.key = (PrivateKey)ks.getKey(alias, pwd.toCharArray()); >> >> this.chain = ks.getCertificateChain(alias); >> >> } >> >> >> >> public PrivateKey getPrivateKey() { >> >> return key; >> >> } >> >> >> >> public Certificate[] getChain() { >> >> return chain; >> >> } >> >> >> >> public Provider getProvider() { >> >> return ks.getProvider(); >> >> } >> >> } >> >> >> >> ===== E N D >> =================================================================== >> >> >> >> >> >> >> >> Martin Brunecky >> Software Architect >> >> RecordFusion >> >> [EMAIL PROTECTED] <mailto:[EMAIL PROTECTED]> >> AIM: mbrunecky >> >> tel: >> fax: >> >> 303-865-8847 x 225 >> <http://www.plaxo.com/click_to_call?src=jj_signature&To=303-86 >> 5-8847+x+225&[EMAIL PROTECTED]> >> 303-865-8846 >> >> >> >> Add me to your address book... >> <https://www.plaxo.com/add_me?u=38655967504&v0=2505222&k0=891308811> >> >> Want a signature like this? <http://www.plaxo.com/signature> >> >> >> >> > > > Aviso Legal: > Esta mensagem é destinada exclusivamente ao destinatário. Pode conter > informação confidencial ou legalmente protegida. A incorrecta transmissão > desta mensagem não significa a perca de confidencialidade. Se esta > mensagem for recebida por engano, por favor envie-a de volta para o > remetente e apague-a do seu sistema de imediato. É proibido a qualquer > pessoa que não o destinatário de usar, revelar ou distribuir qualquer > parte desta mensagem. > > Disclaimer: > This message is destined exclusively to the intended receiver. It may > contain confidential or legally protected information. The incorrect > transmission of this message does not mean the loss of its > confidentiality. If this message is received by mistake, please send it > back to the sender and delete it from your system immediately. It is > forbidden to any person who is not the intended receiver to use, > distribute or copy any part of this message. > > > -----BEGIN PGP SIGNED MESSAGE----- > Hash: SHA1 > > Hi, > > While looking at the signing problems with the USPS EPM system, I've > recently found a modified PdfPKCS7 class send by Aiken Sam sent to the > mailing list in Nov 2006. > > I've tested it and it works fine with demo time stamp servers, i.e. time > stamps are correctly added to the pdf file and Acrobat Reader says the > signature is time stamped (see the demo pdf attached). > > I've modified the code a little bit and created a static inner class > PdfPKCS7.TimeStampServerConnection where the URL and optional username > and password can be specified. A diff against the current version is > attached. > > The timestamp BC jar file will need to be added to the lib directory. > > The user code looks like: > > ============== > PdfPKCS7 pkcs7 = new PdfPKCS7((PrivateKey)signatureKey, certChain, null, > "SHA1", null, false); > > PdfPKCS7.TimeStampServerConnection s; > s = new PdfPKCS7.TimeStampServerConnection(); > > s.setServerURL(TIMESTAMP_SERVER_URL); > s.setUserName(TIMESTAMP_USERNAME); > s.setPassword(TIMESTAMP_PASSWORD); > > pkcs7.setTimeStampServerConnection(s); > > pkcs7.update(content, 0, content.length); > encodedPkcs7 = pkcs7.getEncodedPKCS7(); > > ============== > > The reason I did it that way is that so far I still didn't manage to get > the US postal service EPM system to work and to do that once their > support is a little more responsive, I'll probably need to subclass the > TimeStampServerConnection as their connection method is a little > different. That way, the base class can stay the same, only the > connection details in the subclass will need to be changed. > > > Do you guys agree with that approach or should I just have methods in > PdfPKCS7 instead and three fields for URL, username and passord? > > > Xavier > > - -- > Xavier Le Vourch > Brittany Software, Inc. > <[EMAIL PROTECTED]> > > PGP Key: http://brittanysoftware.com/gpg_key.asc > -----BEGIN PGP SIGNATURE----- > Version: GnuPG v1.4.5 (GNU/Linux) > Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org > > iD8DBQFGezLsA3JYBYlsWUcRAtX9AJ4xRNwj5T+zxJ5RnxoBgfJANp9dLwCeNfpq > GfH4TO3aQ+F0tUI0Xni+kOU= > =aKcB > -----END PGP SIGNATURE----- > > Index: src/com/lowagie/text/pdf/PdfPKCS7.java > =================================================================== > --- src/com/lowagie/text/pdf/PdfPKCS7.java (revision 2850) > +++ src/com/lowagie/text/pdf/PdfPKCS7.java (working copy) > @@ -51,6 +51,11 @@ > import java.io.File; > import java.io.FileInputStream; > import java.io.IOException; > +import java.io.InputStream; > +import java.io.OutputStream; > +import java.math.BigInteger; > +import java.net.URL; > +import java.net.URLConnection; > import java.security.InvalidKeyException; > import java.security.KeyStore; > import java.security.MessageDigest; > @@ -76,8 +81,6 @@ > import java.util.Iterator; > import java.util.Set; > > -import com.lowagie.text.ExceptionConverter; > -import java.math.BigInteger; > import org.bouncycastle.asn1.ASN1EncodableVector; > import org.bouncycastle.asn1.ASN1InputStream; > import org.bouncycastle.asn1.ASN1OutputStream; > @@ -95,10 +98,16 @@ > import org.bouncycastle.asn1.DERString; > import org.bouncycastle.asn1.DERTaggedObject; > import org.bouncycastle.asn1.DERUTCTime; > +import org.bouncycastle.asn1.x509.X509ObjectIdentifiers; > import org.bouncycastle.jce.provider.X509CRLParser; > import org.bouncycastle.jce.provider.X509CertParser; > +import org.bouncycastle.tsp.TimeStampRequest; > +import org.bouncycastle.tsp.TimeStampRequestGenerator; > import org.bouncycastle.util.StreamParsingException; > +import org.bouncycastle.util.encoders.Base64; > > +import com.lowagie.text.ExceptionConverter; > + > /** > * This class does all the processing related to signing and verifying a > PKCS#7 > * signature. > @@ -123,7 +132,8 @@ > private boolean verifyResult; > private byte externalDigest[]; > private byte externalRSAdata[]; > - > + private TimeStampServerConnection timeStampServerConnection; > + > private static final String ID_PKCS7_DATA = "1.2.840.113549.1.7.1"; > private static final String ID_PKCS7_SIGNED_DATA = > "1.2.840.113549.1.7.2"; > private static final String ID_MD5 = "1.2.840.113549.2.5"; > @@ -879,7 +889,11 @@ > // Add the digest > signerinfo.add(new DEROctetString(digest)); > > - > + if (timeStampServerConnection != null) { > + // Add the time stamp from server > + timeStampServerConnection.addTimeStampInfo(digest, > signerinfo); > + } > + > // Finally build the body out of all the components above > ASN1EncodableVector body = new ASN1EncodableVector(); > body.add(new DERInteger(version)); > @@ -1049,8 +1063,25 @@ > public void setSignName(String signName) { > this.signName = signName; > } > - > + > + > /** > + * Getter for property timeStampServerConnection. > + * @return Value of property timeStampServerConnection. > + */ > + public TimeStampServerConnection getTimeStampServerConnection() { > + return timeStampServerConnection; > + } > + > + /** > + * Setter for property timeStampServerConnection. > + * @param timeStampServerConnection New value of property > timeStampServerConnection. > + */ > + public void setTimeStampServerConnection(TimeStampServerConnection > timeStampServerConnection) { > + this.timeStampServerConnection = timeStampServerConnection; > + } > + > + /** > * a class that holds an X509 name > */ > public static class X509Name { > @@ -1293,4 +1324,170 @@ > return buf.toString().trim(); > } > } > + > + public static class TimeStampServerConnection { > + /** > + * Based on code submitted by Aiken Sam, 2006-11-15. > + */ > + > + /** > + * Optional user name > + */ > + private String userName = null; > + > + /** > + * Optional password > + */ > + private String password = null; > + > + /** > + * URL of the time stamp server > + */ > + private String serverURL = null; > + > + public String getPassword() { > + return password; > + } > + > + public void setPassword(String passwordTimestamp) { > + this.password = passwordTimestamp; > + } > + > + public String getServerURL() { > + return serverURL; > + } > + > + public void setServerURL(String serverTimestamp) { > + this.serverURL = serverTimestamp; > + } > + > + public String getUserName() { > + return userName; > + } > + > + public void setUserName(String usernameTimestamp) { > + this.userName = usernameTimestamp; > + } > + > + /** > + * Add the time stamp response from the server. > + * @param digest digest to time stamp. > + * @param signerInfo time stamp response will be appended to this > signer info. > + */ > + public void addTimeStampInfo(byte[] digest, > + ASN1EncodableVector signerInfo) { > + try { > + byte[] timestampHash = computeHash(digest); > + > + ASN1Sequence seq = getServerResponse(timestampHash); > + > + if (seq != null) { > + insertTimeStampInfo(seq, signerInfo); > + } > + } catch (NoSuchAlgorithmException e) { > + throw new ExceptionConverter(e); > + } catch (IOException ioe) { > + throw new ExceptionConverter(ioe); > + } > + } > + > + /** > + * Compute the hash value locally. > + * @param digest data that needs to be time stamped. > + * @return hash value to be sent to the time stamp server. > + * @throws NoSuchAlgorithmException > + */ > + protected byte[] computeHash(byte[] digest) > + throws NoSuchAlgorithmException { > + return MessageDigest.getInstance("SHA1").digest(digest); > + } > + > + /** > + * Append the time stamp response to the signer info. > + * @param seq time stamp response from server. > + * @param signerInfo time stamp response will be appended to this > signer info. > + */ > + protected void insertTimeStampInfo(ASN1Sequence seq, > + ASN1EncodableVector signerInfo) { > + ASN1EncodableVector unauthAttributes = new > ASN1EncodableVector(); > + > + // time Stamp token : id-aa-timeStampToken da RFC3161, alias > old > + // id-smime-aa-timeStampToken > + ASN1EncodableVector v = new ASN1EncodableVector(); > + v.add(new DERObjectIdentifier("1.2.840.113549.1.9.16.2.14")); > // id-aa-timeStampToken > + > + DERObject timeStampToken = (DERObject) seq.getObjectAt(1); > + v.add(new DERSet(timeStampToken)); > + > + unauthAttributes.add(new DERSequence(v)); > + > + signerInfo.add(new DERTaggedObject(false, 1, new DERSet( > + unauthAttributes))); > + } > + > + /** > + * Connect to the server and get a time stamp response for the > hash value. > + * @param hash value to be time stamped. > + * @return time stamp response as a ASN1Sequence. > + * @throws IOException > + */ > + public ASN1Sequence getServerResponse(byte[] hash) throws > IOException { > + byte[] respBytes = null; > + > + URL url = new URL(serverURL); > + URLConnection openHomeConnection = url.openConnection(); > + > + openHomeConnection.setDoInput(true); > + openHomeConnection.setDoOutput(true); > + openHomeConnection.setUseCaches(false); > + openHomeConnection.setRequestProperty("Content-Type", > + "application/timestamp-query"); > + > openHomeConnection.setRequestProperty("Content-Transfer-Encoding", > + "binary"); > + > + if ((userName != null) && !userName.equals("")) { > + String userPassword = userName + ":" + password; > + openHomeConnection.setRequestProperty("Authorization", > "Basic " > + + Base64.encode(userPassword.getBytes())); > + } > + > + OutputStream out = openHomeConnection.getOutputStream(); > + > + TimeStampRequestGenerator tsqGenerator = new > TimeStampRequestGenerator(); > + tsqGenerator.setCertReq(true); > + BigInteger nonce = > BigInteger.valueOf(System.currentTimeMillis()); > + TimeStampRequest tsrequest = tsqGenerator.generate( > + X509ObjectIdentifiers.id_SHA1.getId(), hash, nonce); > + > + byte[] tsq = tsrequest.getEncoded(); > + > + out.write(tsq); > + out.close(); > + > + InputStream fis = openHomeConnection.getInputStream(); > + byte[] buffer = new byte[1024]; > + > + ByteArrayOutputStream baos = new ByteArrayOutputStream(); > + int bytesRead = 0; > + while ((bytesRead = fis.read(buffer, 0, buffer.length)) >= 0) > { > + baos.write(buffer, 0, bytesRead); > + } > + > + respBytes = baos.toByteArray(); > + > + if ("base64".equalsIgnoreCase(openHomeConnection > + .getContentEncoding())) { > + respBytes = Base64.decode(respBytes); > + } > + > + ASN1InputStream tempstream = new ASN1InputStream( > + new ByteArrayInputStream(respBytes)); > + > + ASN1Sequence seq = (ASN1Sequence) tempstream.readObject(); > + > + return seq; > + } > + > + } > + > } > > > ------------------------------------------------------------------------- > This SF.net email is sponsored by: Splunk Inc. > Still grepping through log files to find problems? Stop. > Now Search log events and configuration files using AJAX and a browser. > Download your FREE copy of Splunk now >> http://get.splunk.com/ > _______________________________________________ > iText-questions mailing list > [email protected] > https://lists.sourceforge.net/lists/listinfo/itext-questions > Buy the iText book: http://itext.ugent.be/itext-in-action/ > > -- View this message in context: http://www.nabble.com/PDF-Digital-signature-with-timestamp---using-Time-Stamp-Authority---example-tp11798693p19086070.html Sent from the iText - General mailing list archive at Nabble.com. ------------------------------------------------------------------------- This SF.Net email is sponsored by the Moblin Your Move Developer's challenge Build the coolest Linux based applications with Moblin SDK & win great prizes Grand prize is a trip for two to an Open Source event anywhere in the world http://moblin-contest.org/redirect.php?banner_id=100&url=/ _______________________________________________ iText-questions mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/itext-questions Buy the iText book: http://www.1t3xt.com/docs/book.php
