001/** 002 * Copyright 2014 Emmanuel Bourg 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package net.jsign.timestamp; 018 019import java.io.ByteArrayOutputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.net.HttpURLConnection; 023import java.util.Collection; 024 025import org.bouncycastle.asn1.DERSet; 026import org.bouncycastle.asn1.cms.Attribute; 027import org.bouncycastle.asn1.cms.AttributeTable; 028import org.bouncycastle.asn1.cms.CMSAttributes; 029import org.bouncycastle.cert.X509CertificateHolder; 030import org.bouncycastle.cms.CMSSignedData; 031import org.bouncycastle.cms.SignerInformation; 032import org.bouncycastle.util.encoders.Base64; 033 034import net.jsign.DigestAlgorithm; 035import net.jsign.asn1.authenticode.AuthenticodeTimeStampRequest; 036 037/** 038 * Legacy Authenticode timestamping. 039 * 040 * @author Emmanuel Bourg 041 * @since 1.3 042 */ 043public class AuthenticodeTimestamper extends Timestamper { 044 045 public AuthenticodeTimestamper() { 046 setURL("http://timestamp.sectigo.com"); 047 } 048 049 protected CMSSignedData timestamp(DigestAlgorithm algo, byte[] encryptedDigest) throws IOException, TimestampingException { 050 AuthenticodeTimeStampRequest timestampRequest = new AuthenticodeTimeStampRequest(encryptedDigest); 051 052 byte[] request = Base64.encode(timestampRequest.getEncoded("DER")); 053 054 HttpURLConnection conn = (HttpURLConnection) tsaurl.openConnection(); 055 conn.setConnectTimeout(10000); 056 conn.setReadTimeout(10000); 057 conn.setDoOutput(true); 058 conn.setDoInput(true); 059 conn.setUseCaches(false); 060 conn.setRequestMethod("POST"); 061 conn.setRequestProperty("Content-type", "application/octet-stream"); 062 conn.setRequestProperty("Content-length", String.valueOf(request.length)); 063 conn.setRequestProperty("Accept", "application/octet-stream"); 064 conn.setRequestProperty("User-Agent", "Transport"); 065 066 conn.getOutputStream().write(request); 067 conn.getOutputStream().flush(); 068 069 if (conn.getResponseCode() >= 400) { 070 throw new IOException("Unable to complete the timestamping due to HTTP error: " + conn.getResponseCode() + " - " + conn.getResponseMessage()); 071 } 072 073 try { 074 byte[] response = Base64.decode(toBytes(conn.getInputStream())); 075 return new CMSSignedData(response); 076 } catch (Exception e) { 077 throw new TimestampingException("Unable to complete the timestamping", e); 078 } 079 } 080 081 @Override 082 protected Collection<X509CertificateHolder> getExtraCertificates(CMSSignedData token) { 083 return token.getCertificates().getMatches(null); 084 } 085 086 @Override 087 protected AttributeTable getUnsignedAttributes(CMSSignedData token) { 088 SignerInformation timestampSignerInformation = token.getSignerInfos().getSigners().iterator().next(); 089 Attribute counterSignature = new Attribute(CMSAttributes.counterSignature, new DERSet(timestampSignerInformation.toASN1Structure())); 090 091 return new AttributeTable(counterSignature); 092 } 093 094 private byte[] toBytes(InputStream in) throws IOException { 095 ByteArrayOutputStream bout = new ByteArrayOutputStream(); 096 097 byte[] buffer = new byte[4096]; 098 int n; 099 while ((n = in.read(buffer)) != -1) { 100 bout.write(buffer, 0, n); 101 } 102 103 return bout.toByteArray(); 104 } 105}