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

Reply via email to