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}