1 package org.ejbca.appserver.jboss; 2 3 import java.io.ByteArrayInputStream ; 4 import java.io.ByteArrayOutputStream ; 5 import java.io.File ; 6 import java.io.FileOutputStream ; 7 import java.io.IOException ; 8 import java.io.InputStream ; 9 import java.math.BigInteger ; 10 import java.security.MessageDigest ; 11 import java.text.SimpleDateFormat ; 12 import java.util.Date ; 13 import java.util.Locale ; 14 import java.util.Random ; 15 import java.util.TimeZone ; 16 17 import org.apache.commons.httpclient.HttpClient; 18 import org.apache.commons.httpclient.HttpStatus; 19 import org.apache.commons.httpclient.methods.PostMethod; 20 import org.apache.log4j.FileAppender; 21 import org.apache.log4j.Layout; 22 import org.apache.log4j.helpers.LogLog; 23 import org.apache.log4j.spi.LoggingEvent; 24 import org.bouncycastle.tsp.TSPAlgorithms; 25 import org.bouncycastle.tsp.TSPException; 26 import org.bouncycastle.tsp.TSPValidationException; 27 import org.bouncycastle.tsp.TimeStampRequest; 28 import org.bouncycastle.tsp.TimeStampRequestGenerator; 29 import org.bouncycastle.tsp.TimeStampResponse; 30 import org.bouncycastle.util.encoders.Base64; 31 import org.ejbca.util.CertTools; 32 import org.ejbca.util.FileTools; 33 34 43 public class SigningDailyRollingFileAppender extends FileAppender { 44 45 private Thread signerThread; 46 47 51 private String datePattern = "'.'yyyy-MM-dd"; 52 53 54 private String signMethod; 55 56 57 private String tsaUrl; 58 59 69 private String scheduledFilename; 70 71 73 private long nextCheck = System.currentTimeMillis () - 1; 74 75 Date now = new Date (); 76 77 SimpleDateFormat sdf; 78 79 RollingCalendar rc = new RollingCalendar(); 80 81 int checkPeriod = RollingCalendar.TOP_OF_TROUBLE; 82 83 static final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT"); 85 86 87 89 public SigningDailyRollingFileAppender() { 90 } 91 92 98 public SigningDailyRollingFileAppender (Layout layout, String filename, 99 String datePattern) throws IOException { 100 super(layout, filename, true); 101 this.datePattern = datePattern; 102 activateOptions(); 103 } 104 105 108 public void setFile(final String filename) 109 { 110 org.jboss.logging.appender.FileAppender.Helper.makePath(filename); 111 super.setFile(filename); 112 } 113 114 119 public void setDatePattern(String pattern) { 120 datePattern = pattern; 121 } 122 123 124 public String getDatePattern() { 125 return datePattern; 126 } 127 128 public void setSignMethod(String method) { 129 signMethod = method; 130 } 131 public String getSignMethod() { 132 return signMethod; 133 } 134 public void setTsaUrl(String url) { 135 tsaUrl = url; 136 } 137 public String getTsaUrl() { 138 return tsaUrl; 139 } 140 141 public void activateOptions() { 142 super.activateOptions(); 143 if(datePattern != null && fileName != null) { 144 now.setTime(System.currentTimeMillis()); 145 sdf = new SimpleDateFormat (datePattern); 146 int type = computeCheckPeriod(); 147 printPeriodicity(type); 148 rc.setType(type); 149 File file = new File (fileName); 150 scheduledFilename = fileName+sdf.format(new Date (file.lastModified())); 151 152 } else { 153 LogLog.error("Either File or DatePattern options are not set for appender ["+name+"]."); 154 } 155 if (signMethod != null) { 156 if (tsaUrl == null) { 157 LogLog.error("TsaUrl option is not set for appender ["+name+"]."); 158 } 159 } else { 160 LogLog.error("SignMethod option is not set for appender ["+name+"]."); 161 } 162 CertTools.installBCProvider(); 163 } 164 165 void printPeriodicity(int type) { 166 switch(type) { 167 case RollingCalendar.TOP_OF_MINUTE: 168 LogLog.debug("Appender ["+name+"] to be rolled every minute."); 169 break; 170 case RollingCalendar.TOP_OF_HOUR: 171 LogLog.debug("Appender ["+name 172 +"] to be rolled on top of every hour."); 173 break; 174 case RollingCalendar.HALF_DAY: 175 LogLog.debug("Appender ["+name 176 +"] to be rolled at midday and midnight."); 177 break; 178 case RollingCalendar.TOP_OF_DAY: 179 LogLog.debug("Appender ["+name 180 +"] to be rolled at midnight."); 181 break; 182 case RollingCalendar.TOP_OF_WEEK: 183 LogLog.debug("Appender ["+name 184 +"] to be rolled at start of week."); 185 break; 186 case RollingCalendar.TOP_OF_MONTH: 187 LogLog.debug("Appender ["+name 188 +"] to be rolled at start of every month."); 189 break; 190 default: 191 LogLog.warn("Unknown periodicity for appender ["+name+"]."); 192 } 193 } 194 195 196 205 int computeCheckPeriod() { 206 RollingCalendar rollingCalendar = new RollingCalendar(gmtTimeZone, Locale.ENGLISH); 207 Date epoch = new Date (0); 209 if(datePattern != null) { 210 for(int i = RollingCalendar.TOP_OF_MINUTE; i <= RollingCalendar.TOP_OF_MONTH; i++) { 211 SimpleDateFormat simpleDateFormat = new SimpleDateFormat (datePattern); 212 simpleDateFormat.setTimeZone(gmtTimeZone); String r0 = simpleDateFormat.format(epoch); 214 rollingCalendar.setType(i); 215 Date next = new Date (rollingCalendar.getNextCheckMillis(epoch)); 216 String r1 = simpleDateFormat.format(next); 217 if(r0 != null && r1 != null && !r0.equals(r1)) { 219 return i; 220 } 221 } 222 } 223 return RollingCalendar.TOP_OF_TROUBLE; } 225 226 229 void rollOver() throws IOException { 230 231 232 if (datePattern == null) { 233 errorHandler.error("Missing DatePattern option in rollOver()."); 234 return; 235 } 236 237 String datedFilename = fileName+sdf.format(now); 238 if (scheduledFilename.equals(datedFilename)) { 242 return; 243 } 244 245 this.closeFile(); 247 248 File target = new File (scheduledFilename); 249 if (target.exists()) { 250 target.delete(); 251 } 252 253 File file = new File (fileName); 254 boolean result = file.renameTo(target); 255 if(result) { 256 LogLog.debug(fileName +" -> "+ scheduledFilename); 257 } else { 258 LogLog.error("Failed to rename ["+fileName+"] to ["+scheduledFilename+"]."); 259 } 260 261 try { 262 this.setFile(fileName, false, this.bufferedIO, this.bufferSize); 265 } 266 catch(IOException e) { 267 errorHandler.error("setFile("+fileName+", false) call failed."); 268 } 269 if (signMethod.equalsIgnoreCase("tsa")) { 270 if (tsaUrl != null) { 271 if ( (signerThread != null) && signerThread.isAlive() ) { 274 System.out.println("Stopping old hanging signerthread"); 275 signerThread.interrupt(); 276 } 277 signerThread = new Thread (new SignerThread(tsaUrl, scheduledFilename, scheduledFilename+".tsp")); 278 signerThread.start(); 279 } else { 280 System.out.println("No TsaUrl set, can not sign logs!"); 281 } 282 } 283 284 scheduledFilename = datedFilename; 285 } 286 287 295 protected void subAppend(LoggingEvent event) { 296 long n = System.currentTimeMillis(); 297 if (n >= nextCheck) { 298 now.setTime(n); 299 nextCheck = rc.getNextCheckMillis(now); 300 try { 301 rollOver(); 302 } 303 catch(IOException ioe) { 304 LogLog.error("rollOver() failed.", ioe); 305 } 306 } 307 super.subAppend(event); 308 } 309 310 } 311 312 class SignerThread implements Runnable { 313 private String urlstr; 314 private String infile; 315 private String outfile; 316 public SignerThread(String urlstr, String infile, String outfile) { 317 this.urlstr = urlstr; 318 this.infile = infile; 319 this.outfile = outfile; 320 } 321 public void run() { 322 323 try { 324 boolean base64 = true; 325 TimeStampRequestGenerator timeStampRequestGenerator = new TimeStampRequestGenerator(); 326 327 Random rand = new Random (); 328 int nonce = rand.nextInt(); 329 byte[] digestBytes = new byte[20]; 330 if (infile != null) { 331 digestBytes = FileTools.readFiletoBuffer(infile); 332 } 333 MessageDigest dig = MessageDigest.getInstance(TSPAlgorithms.SHA1, "BC"); 334 dig.update(digestBytes); 335 byte[] digest = dig.digest(); 336 TimeStampRequest timeStampRequest = timeStampRequestGenerator.generate(TSPAlgorithms.SHA1, digest, BigInteger.valueOf(nonce)); 337 338 HttpClient client = new HttpClient(); 340 341 client.setConnectionTimeout(5000); 343 PostMethod method = new PostMethod(urlstr); 345 method.setParameter("http.socket.timeout", "5000"); 346 method.setRequestHeader("Content-Type", "application/timestamp-query"); 347 method.setRequestBody(new ByteArrayInputStream (timeStampRequest.getEncoded())); 348 InputStream input = null; 351 ByteArrayOutputStream baos = null; 352 byte[] replyBytes = null; 353 try { 354 client.executeMethod(method); 355 if (method.getStatusCode() == HttpStatus.SC_OK) { 356 replyBytes = method.getResponseBody(); 357 } 358 } catch (Exception e) { 359 e.printStackTrace(); 360 } finally { 361 method.releaseConnection(); 362 if (input != null) input.close(); 363 if (baos != null) baos.close(); 364 } 365 366 if ( (outfile != null) && (replyBytes != null) ) { 367 byte[] outBytes; 369 if (base64) { 370 outBytes=Base64.encode(replyBytes); 371 } else { 372 outBytes = replyBytes; 373 } 374 FileOutputStream fos = null; 375 try { 376 fos = new FileOutputStream (outfile); 377 fos.write(outBytes); 378 } finally { 379 if (fos != null) fos.close(); 380 } 381 } 382 383 if (replyBytes != null) { 384 try { 385 TimeStampResponse timeStampResponse = new TimeStampResponse(replyBytes); 386 timeStampResponse.validate(timeStampRequest); 387 } catch (TSPValidationException e) { 388 LogLog.error("TimeStampResponse validation failed.", e); 389 e.printStackTrace(); 390 } catch (TSPException e) { 391 LogLog.error("TimeStampResponse failed.", e); 392 e.printStackTrace(); 393 } 394 } else { 395 LogLog.error("No reply bytes received, is TSA down?"); 396 System.out.println("SigningDailyRollingFileAppender: No reply bytes received, is TSA down?"); 397 } 398 } catch (Exception e) { 399 LogLog.error("Exception caught while signing log: ", e); 400 e.printStackTrace(); 401 } 402 403 } 404 405 } 406 407 | Popular Tags |