001/*
002 * $Id: TSAClientBouncyCastle.java 4784 2011-03-15 08:33:00Z blowagie $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044package com.itextpdf.text.pdf;
045
046import java.io.*;
047import java.math.*;
048import java.net.*;
049import com.itextpdf.text.error_messages.MessageLocalization;
050
051import org.bouncycastle.asn1.cmp.*;
052import org.bouncycastle.asn1.x509.*;
053import org.bouncycastle.tsp.*;
054
055import com.itextpdf.text.pdf.codec.Base64;
056
057/**
058 * Time Stamp Authority Client interface implementation using Bouncy Castle
059 * org.bouncycastle.tsp package.
060 * <p>
061 * Created by Aiken Sam, 2006-11-15, refactored by Martin Brunecky, 07/15/2007
062 * for ease of subclassing.
063 * </p>
064 * @since       2.1.6
065 */
066public class TSAClientBouncyCastle implements TSAClient {
067    /** URL of the Time Stamp Authority */
068        protected String tsaURL;
069        /** TSA Username */
070    protected String tsaUsername;
071    /** TSA password */
072    protected String tsaPassword;
073    /** Estimate of the received time stamp token */
074    protected int tokSzEstimate;
075    
076    /**
077     * Creates an instance of a TSAClient that will use BouncyCastle.
078     * @param url String - Time Stamp Authority URL (i.e. "http://tsatest1.digistamp.com/TSA")
079     */
080    public TSAClientBouncyCastle(String url) {
081        this(url, null, null, 4096);
082    }
083    
084    /**
085     * Creates an instance of a TSAClient that will use BouncyCastle.
086     * @param url String - Time Stamp Authority URL (i.e. "http://tsatest1.digistamp.com/TSA")
087     * @param username String - user(account) name
088     * @param password String - password
089     */
090    public TSAClientBouncyCastle(String url, String username, String password) {
091        this(url, username, password, 4096);
092    }
093    
094    /**
095     * Constructor.
096     * Note the token size estimate is updated by each call, as the token
097     * size is not likely to change (as long as we call the same TSA using
098     * the same imprint length).
099     * @param url String - Time Stamp Authority URL (i.e. "http://tsatest1.digistamp.com/TSA")
100     * @param username String - user(account) name
101     * @param password String - password
102     * @param tokSzEstimate int - estimated size of received time stamp token (DER encoded)
103     */
104    public TSAClientBouncyCastle(String url, String username, String password, int tokSzEstimate) {
105        this.tsaURL       = url;
106        this.tsaUsername  = username;
107        this.tsaPassword  = password;
108        this.tokSzEstimate = tokSzEstimate;
109    }
110    
111    /**
112     * Get the token size estimate.
113     * Returned value reflects the result of the last succesfull call, padded
114     * @return an estimate of the token size
115     */
116    public int getTokenSizeEstimate() {
117        return tokSzEstimate;
118    }
119    
120    /**
121     * Get RFC 3161 timeStampToken.
122     * Method may return null indicating that timestamp should be skipped.
123     * @param caller PdfPKCS7 - calling PdfPKCS7 instance (in case caller needs it)
124     * @param imprint byte[] - data imprint to be time-stamped
125     * @return byte[] - encoded, TSA signed data of the timeStampToken
126     * @throws Exception - TSA request failed
127     * @see com.itextpdf.text.pdf.TSAClient#getTimeStampToken(com.itextpdf.text.pdf.PdfPKCS7, byte[])
128     */
129    public byte[] getTimeStampToken(PdfPKCS7 caller, byte[] imprint) throws Exception {
130        return getTimeStampToken(imprint);
131    }
132    
133    /**
134     * Get timestamp token - Bouncy Castle request encoding / decoding layer
135     */
136    protected byte[] getTimeStampToken(byte[] imprint) throws Exception {
137        byte[] respBytes = null;
138        try {
139            // Setup the time stamp request
140            TimeStampRequestGenerator tsqGenerator = new TimeStampRequestGenerator();
141            tsqGenerator.setCertReq(true);
142            // tsqGenerator.setReqPolicy("1.3.6.1.4.1.601.10.3.1");
143            BigInteger nonce = BigInteger.valueOf(System.currentTimeMillis());
144            TimeStampRequest request = tsqGenerator.generate(X509ObjectIdentifiers.id_SHA1.getId() , imprint, nonce);
145            byte[] requestBytes = request.getEncoded();
146            
147            // Call the communications layer
148            respBytes = getTSAResponse(requestBytes);
149            
150            // Handle the TSA response
151            TimeStampResponse response = new TimeStampResponse(respBytes);
152            
153            // validate communication level attributes (RFC 3161 PKIStatus)
154            response.validate(request);
155            PKIFailureInfo failure = response.getFailInfo();
156            int value = (failure == null) ? 0 : failure.intValue();
157            if (value != 0) {
158                // @todo: Translate value of 15 error codes defined by PKIFailureInfo to string
159                throw new Exception(MessageLocalization.getComposedMessage("invalid.tsa.1.response.code.2", tsaURL, String.valueOf(value)));
160            }
161            // @todo: validate the time stap certificate chain (if we want
162            //        assure we do not sign using an invalid timestamp).
163            
164            // extract just the time stamp token (removes communication status info)
165            TimeStampToken  tsToken = response.getTimeStampToken();
166            if (tsToken == null) {
167                throw new Exception(MessageLocalization.getComposedMessage("tsa.1.failed.to.return.time.stamp.token.2", tsaURL, response.getStatusString()));
168            }
169            TimeStampTokenInfo info = tsToken.getTimeStampInfo(); // to view details
170            byte[] encoded = tsToken.getEncoded();
171            long stop = System.currentTimeMillis();
172            
173            // Update our token size estimate for the next call (padded to be safe)
174            this.tokSzEstimate = encoded.length + 32;
175            return encoded;
176        } catch (Exception e) {
177            throw e;
178        } catch (Throwable t) {
179            throw new Exception(MessageLocalization.getComposedMessage("failed.to.get.tsa.response.from.1", tsaURL), t);
180        }
181    }
182    
183    /**
184     * Get timestamp token - communications layer
185     * @return - byte[] - TSA response, raw bytes (RFC 3161 encoded)
186     */
187    protected byte[] getTSAResponse(byte[] requestBytes) throws Exception {
188        // Setup the TSA connection
189        URL url = new URL(tsaURL);
190        URLConnection tsaConnection;
191        tsaConnection = (URLConnection) url.openConnection();
192        
193        tsaConnection.setDoInput(true);
194        tsaConnection.setDoOutput(true);
195        tsaConnection.setUseCaches(false);
196        tsaConnection.setRequestProperty("Content-Type", "application/timestamp-query");
197        //tsaConnection.setRequestProperty("Content-Transfer-Encoding", "base64");
198        tsaConnection.setRequestProperty("Content-Transfer-Encoding", "binary");
199        
200        if ((tsaUsername != null) && !tsaUsername.equals("") ) {
201            String userPassword = tsaUsername + ":" + tsaPassword;
202            tsaConnection.setRequestProperty("Authorization", "Basic " +
203                Base64.encodeBytes(userPassword.getBytes()));
204        }
205        OutputStream out = tsaConnection.getOutputStream();
206        out.write(requestBytes);
207        out.close();
208        
209        // Get TSA response as a byte array
210        InputStream inp = tsaConnection.getInputStream();
211        ByteArrayOutputStream baos = new ByteArrayOutputStream();
212        byte[] buffer = new byte[1024];
213        int bytesRead = 0;
214        while ((bytesRead = inp.read(buffer, 0, buffer.length)) >= 0) {
215            baos.write(buffer, 0, bytesRead);
216        }
217        byte[] respBytes = baos.toByteArray();
218        
219        String encoding = tsaConnection.getContentEncoding();
220        if (encoding != null && encoding.equalsIgnoreCase("base64")) {
221            respBytes = Base64.decode(new String(respBytes));
222        }
223        return respBytes;
224    }    
225}