/**
   * sends TS Request and receives an answer in following definition
   *
   * <p>The TimeStampResp ASN.1 type definition:
   *
   * <pre>
   *
   *     TimeStampResp ::= SEQUENCE {
   *         status            PKIStatusInfo,
   *         timeStampToken    TimeStampToken OPTIONAL ]
   *
   *     PKIStatusInfo ::= SEQUENCE {
   *         status        PKIStatus,
   *         statusString  PKIFreeText OPTIONAL,
   *         failInfo      PKIFailureInfo OPTIONAL }
   *
   *     PKIStatus ::= INTEGER {
   *         granted                (0),
   *           -- when the PKIStatus contains the value zero a TimeStampToken, as
   *           -- requested, is present.
   *         grantedWithMods        (1),
   *           -- when the PKIStatus contains the value one a TimeStampToken,
   *           -- with modifications, is present.
   *         rejection              (2),
   *         waiting                (3),
   *         revocationWarning      (4),
   *           -- this message contains a warning that a revocation is
   *           -- imminent
   *         revocationNotification (5)
   *           -- notification that a revocation has occurred }
   *
   *     PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String
   *           -- text encoded as UTF-8 String (note:  each UTF8String SHOULD
   *           -- include an RFC 1766 language tag to indicate the language
   *           -- of the contained text)
   *
   *     PKIFailureInfo ::= BIT STRING {
   *         badAlg              (0),
   *           -- unrecognized or unsupported Algorithm Identifier
   *         badRequest          (2),
   *           -- transaction not permitted or supported
   *         badDataFormat       (5),
   *           -- the data submitted has the wrong format
   *         timeNotAvailable    (14),
   *           -- the TSA's time source is not available
   *         unacceptedPolicy    (15),
   *           -- the requested TSA policy is not supported by the TSA
   *         unacceptedExtension (16),
   *           -- the requested extension is not supported by the TSA
   *         addInfoNotAvailable (17)
   *           -- the additional information requested could not be understood
   *           -- or is not available
   *         systemFailure       (25)
   *           -- the request cannot be handled due to system failure }
   *
   *     TimeStampToken ::= ContentInfo
   *         -- contentType is id-signedData
   *         -- content is SignedData
   *         -- eContentType within SignedData is id-ct-TSTInfo
   *         -- eContent within SignedData is TSTInfo
   *
   * </pre>
   *
   * @param tsq TimeStamp Request to be sent to TSA
   * @param server complete URL of the TSA server
   * @return TimeStamp Response created from TSA's response
   */
  private TimeStampResponse getTSResponse(TimeStampRequest tsq, String server)
      throws IOException, TSPException {
    logger.trace("entering getTSResponse() method");
    logger.entry(tsq, server);
    final byte[] request = tsq.getEncoded();
    // open valid connection
    HttpURLConnection con;
    try {
      URL url = new URL(server);
      con = (HttpURLConnection) url.openConnection();
    } catch (IOException e) {
      logger.error("TSA server couldn't be contacted");
      throw e;
    }
    con.setDoOutput(true);
    con.setDoInput(true);
    con.setRequestProperty("Content-type", "application/timestamp-query");
    // con.setRequestProperty("Content-length", String.valueOf(request.length));
    logger.info("TSA server was successfully contacted");

    // send request
    OutputStream out;
    try {
      out = con.getOutputStream();
      out.write(request);
      out.flush();
    } catch (IOException e) {
      logger.error("Failed to send the TS request.");
      throw e;
    }
    logger.debug("TS request sent");

    // receive response
    InputStream in;
    TimeStampResp resp;
    TimeStampResponse response;
    try {
      // verify connection status
      if (con.getResponseCode() != HttpURLConnection.HTTP_OK) {
        throw new IOException(
            "Received HTTP error: " + con.getResponseCode() + " - " + con.getResponseMessage());
      } else {
        logger.debug("Response Code: {}", con.getResponseCode());
      }
      // accept the answer
      in = con.getInputStream();
      resp = TimeStampResp.getInstance(new ASN1InputStream(in).readObject());
      response = new TimeStampResponse(resp);
      // verify the answer
      response.validate(tsq);
    } catch (TSPException | IOException e) {
      logger.error("Cannot interpret incoming data.");
      throw e;
    }

    logger.debug("Status: {}", response.getStatusString()); // null means OK

    return logger.exit(response);
  }
  /**
   * Main method for stamping given file. It serves as an example of whole process in one go.
   *
   * @param filename
   * @param is
   * @return
   * @throws java.io.IOException
   * @throws org.bouncycastle.tsp.TSPException
   */
  public byte[] stamp(String filename, InputStream is) throws IOException, TSPException {
    logger.entry();

    logger.info("File to be stamped: {}", filename);
    byte[] file = getBytesFromInputStream(is);

    ExtendedDigest digestAlgorithm = new SHA256Digest(); // select hash algorithm
    ASN1ObjectIdentifier requestAlgorithm;
    try {
      requestAlgorithm = getHashObjectIdentifier(digestAlgorithm.getAlgorithmName());
    } catch (IllegalArgumentException e) {
      logger.catching(e);
      throw e;
    }
    logger.info("Selected algorithm: {}", digestAlgorithm.getAlgorithmName());

    // create request
    byte[] digest = calculateMessageDigest(file, digestAlgorithm);
    TimeStampRequest tsq = getTSRequest(digest, requestAlgorithm);
    logger.debug("TS request generated");

    // send request and receive response
    TimeStampResponse tsr;
    try {
      tsr = getTSResponse(tsq, server);
    } catch (IOException | TSPException e) {
      logger.catching(e);
      throw e;
    }
    logger.debug("TSA response received");

    // log reason of failure
    if (tsr.getFailInfo() != null) {
      logFailReason(tsr.getFailInfo().intValue());
      return null;
    }

    // log response
    logResponse(tsr);

    logger.exit();
    return tsr.getEncoded();
  }
  /**
   * method for JSP to create valid TimeStampRequest
   *
   * @param file
   * @param tsr
   * @return
   */
  public TimeStampRequest createCorrespondingTSQ(byte[] file, TimeStampResponse tsr) {
    // get hash algorithm
    ASN1ObjectIdentifier algID =
        tsr.getTimeStampToken().getTimeStampInfo().getMessageImprintAlgOID();
    GeneralDigest digestAlgorithm = getDigestAlg(algID);

    logger.info(
        "The timestamp's algorithm was recognized as {}.", digestAlgorithm.getAlgorithmName());

    // create new hashed request
    byte[] digest = calculateMessageDigest(file, digestAlgorithm);
    TimeStampRequest tsq = getTSRequest(digest, algID);

    return tsq;
  }
 /**
  * prints details of received TimeStamp
  *
  * @param tsr {@link TimeStampResponse}
  */
 private void logResponse(final TimeStampResponse tsr) {
   logger.info("Timestamp: {}", tsr.getTimeStampToken().getTimeStampInfo().getGenTime());
   logger.info("TSA: {}", tsr.getTimeStampToken().getTimeStampInfo().getTsa());
   logger.info("Serial number: {}", tsr.getTimeStampToken().getTimeStampInfo().getSerialNumber());
   logger.info("Policy: {}", tsr.getTimeStampToken().getTimeStampInfo().getPolicy());
 }
  /**
   * Main method for validating files with stamps. It serves as an example of whole process in one
   * go.
   *
   * @param originalFile
   * @param timeStamp
   * @throws IOException
   * @throws TSPException
   * @throws CertificateException
   * @throws CertificateEncodingException
   * @throws OperatorCreationException
   */
  public void verify(InputStream originalFile, InputStream timeStamp)
      throws IOException, TSPException, CertificateException, CertificateEncodingException,
          OperatorCreationException {
    logger.entry();

    // open files
    byte[] file = getBytesFromInputStream(originalFile);
    TimeStampResponse tsr = parseTSR(timeStamp);

    logger.info("The timestamp was sucessfully opened.");
    logResponse(tsr);

    // get hash algorithm
    ASN1ObjectIdentifier algID =
        tsr.getTimeStampToken().getTimeStampInfo().getMessageImprintAlgOID();
    GeneralDigest digestAlgorithm = getDigestAlg(algID);

    logger.info(
        "The timestamp's algorithm was recognized as {}.", digestAlgorithm.getAlgorithmName());

    // create new hashed request
    byte[] digest = calculateMessageDigest(file, digestAlgorithm);
    TimeStampRequest tsq = getTSRequest(digest, algID);

    logger.info(
        "The timestamp fits the file (the file was not changed), now verifying certificates..");

    InputStream is = null;

    if (is == null) {
      throw new UnsupportedOperationException(
          "Timestamp fits the file (the file was not changed), certificate not implemented yet.");
    }

    // <editor-fold defaultstate="collapsed" desc="varianta 1 - ze souboru">
    CertificateFactory factory;
    X509Certificate cert;
    try {
      factory = CertificateFactory.getInstance("X.509");
      cert = (X509Certificate) factory.generateCertificate(null); // TODO: get inputstream
    } catch (CertificateException e) {
      logger.catching(e);
      throw e;
    }

    // RSA Signature processing with BC
    X509CertificateHolder holder;
    SignerInformationVerifier siv;
    try {
      holder = new X509CertificateHolder(cert.getEncoded());
      siv =
          new BcRSASignerInfoVerifierBuilder(
                  new DefaultCMSSignatureAlgorithmNameGenerator(),
                      new DefaultSignatureAlgorithmIdentifierFinder(),
                  new DefaultDigestAlgorithmIdentifierFinder(), new BcDigestCalculatorProvider())
              .build(holder);
    } catch (CertificateEncodingException | OperatorCreationException e) {
      logger.catching(e);
      throw e;
    }

    // Signature processing with JCA and other provider
    // X509CertificateHolder holderJca = new JcaX509CertificateHolder(cert);
    // SignerInformationVerifier sivJca = new
    // JcaSimpleSignerInfoVerifierBuilder().setProvider("anotherprovider").build(holderJca);

    try {
      tsr.getTimeStampToken().validate(siv);
    } catch (TSPException e) {
      logger.catching(e);
      throw e;
    }
    /* konec varianty 1 */
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="varianta 2 - z razitka">
    /*/ get certificates
            CollectionStore certs = (CollectionStore) tst.getCertificates();
            SignerInformationStore signers = tst.toCMSSignedData().getSignerInfos();

            int certsCount = 0;
            for (Iterator it = certs.iterator(); it.hasNext();) {
                certsCount++; // stupid hack to get actual number of certificates
            }
            if (certsCount < 1) {
                logger.error("No certificate found.");
                return;
            }

            for (SignerInformation signer : signers) {
                Collection<X509Certificate> col = certs.getMatches(signer.getSID());
                X509Certificate[] signerCerts = new X509Certificate[0];
                col.toArray(signerCerts);

                if (signerCerts.length != 1) {
                    logger.error("Expected only one certificate per signer.");
                    return;
                }

                try {
                    signerCerts[0].checkValidity();
                } catch (CertificateExpiredException | CertificateNotYetValidException ex) {
                    java.util.logging.Logger.getLogger(TSAConnector.class.getName()).log(Level.SEVERE, null, ex);
                }

    //            signer.verify(signerCerts[0]); // should verify that the timestamp is signed correctly
            }
            /* konec varianty 2 */
    // </editor-fold>

    logger.info("All certificates successfully verified, the timestamp is trusted.");
    logger.exit();
  }
  public byte[] getTimeStampToken(byte[] digest)
      throws NoSuchAlgorithmException, UnsupportedEncodingException, TSPException {

    TimeStampRequestGenerator tsqGenerator = new TimeStampRequestGenerator();
    tsqGenerator.setCertReq(true);
    if (tsaOid != null) tsqGenerator.setReqPolicy(tsaOid);
    TimeStampRequest tsReq =
        tsqGenerator.generate(TSPAlgorithms.SHA1, digest, BigInteger.valueOf(100));
    byte[] respBytes;
    try {
      byte[] requestBytes = tsReq.getEncoded();
      URL url = new URL(tsaURL);
      HttpsURLConnection tsaConnection = (HttpsURLConnection) url.openConnection();
      String user_pass = Base64.encodeToString((tsaUsername + ":" + tsaPassword).getBytes(), 0);
      tsaConnection.setRequestProperty("Authorization", "Basic " + user_pass);
      tsaConnection.setDoInput(true);
      tsaConnection.setDoOutput(true);
      tsaConnection.setUseCaches(false);
      tsaConnection.setRequestProperty("Content-Type", "application/timestamp-query");
      tsaConnection.setRequestProperty("Content-Transfer-Encoding", "binary");
      OutputStream out = tsaConnection.getOutputStream();
      out.write(requestBytes);
      out.close();
      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);
      }
      respBytes = baos.toByteArray();
      String encoding = tsaConnection.getContentEncoding();
      if (encoding != null && encoding.equalsIgnoreCase("base64")) {
        respBytes = Base64.decode(new String(respBytes), 0);
      }

      if (respBytes == null) {
        String error = "Error: Impossible to get TSA response";
        Log.e(TAG, error);
      }
      TimeStampResponse tsRes = new TimeStampResponse(respBytes);

      tsRes.validate(tsReq);
      PKIFailureInfo failure = tsRes.getFailInfo();
      int value = (failure == null) ? 0 : failure.intValue();
      if (value != 0) {
        String error = "Error: Invalid TSA response (" + tsRes.getStatusString() + ")";
        Log.e(TAG, error);
        return null;
      }
      TimeStampToken myTSToken = tsRes.getTimeStampToken();
      if (myTSToken == null) {
        String error = "Error: Invalid TSA response (NULL)";
        Log.e(TAG, error);
        return null;
      }
      return myTSToken.getEncoded();
    } catch (IOException | TSPException e) {
      Log.e(TAG, e.getMessage());
    }
    return null;
  }
  public byte[] timeStamp(byte[] data, RevocationData revocationData) throws Exception {
    // digest the message
    MessageDigest messageDigest = MessageDigest.getInstance(this.digestAlgo);
    byte[] digest = messageDigest.digest(data);

    // generate the TSP request
    BigInteger nonce = new BigInteger(128, new SecureRandom());
    TimeStampRequestGenerator requestGenerator = new TimeStampRequestGenerator();
    requestGenerator.setCertReq(true);
    if (null != this.requestPolicy) {
      requestGenerator.setReqPolicy(this.requestPolicy);
    }
    TimeStampRequest request = requestGenerator.generate(this.digestAlgoOid, digest, nonce);
    byte[] encodedRequest = request.getEncoded();

    // create the HTTP client
    HttpClient httpClient = new HttpClient();
    if (null != this.username) {
      Credentials credentials = new UsernamePasswordCredentials(this.username, this.password);
      httpClient.getState().setCredentials(AuthScope.ANY, credentials);
    }
    if (null != this.proxyHost) {
      httpClient.getHostConfiguration().setProxy(this.proxyHost, this.proxyPort);
    }

    // create the HTTP POST request
    PostMethod postMethod = new PostMethod(this.tspServiceUrl);
    RequestEntity requestEntity =
        new ByteArrayRequestEntity(encodedRequest, "application/timestamp-query");
    postMethod.addRequestHeader("User-Agent", this.userAgent);
    postMethod.setRequestEntity(requestEntity);

    // invoke TSP service
    int statusCode = httpClient.executeMethod(postMethod);
    if (HttpStatus.SC_OK != statusCode) {
      LOG.error("Error contacting TSP server " + this.tspServiceUrl);
      throw new Exception("Error contacting TSP server " + this.tspServiceUrl);
    }

    // HTTP input validation
    Header responseContentTypeHeader = postMethod.getResponseHeader("Content-Type");
    if (null == responseContentTypeHeader) {
      throw new RuntimeException("missing Content-Type header");
    }
    String contentType = responseContentTypeHeader.getValue();
    if (!contentType.startsWith("application/timestamp-reply")) {
      LOG.debug("response content: " + postMethod.getResponseBodyAsString());
      throw new RuntimeException("invalid Content-Type: " + contentType);
    }
    if (0 == postMethod.getResponseContentLength()) {
      throw new RuntimeException("Content-Length is zero");
    }

    // TSP response parsing and validation
    InputStream inputStream = postMethod.getResponseBodyAsStream();
    TimeStampResponse timeStampResponse = new TimeStampResponse(inputStream);
    timeStampResponse.validate(request);

    if (0 != timeStampResponse.getStatus()) {
      LOG.debug("status: " + timeStampResponse.getStatus());
      LOG.debug("status string: " + timeStampResponse.getStatusString());
      PKIFailureInfo failInfo = timeStampResponse.getFailInfo();
      if (null != failInfo) {
        LOG.debug("fail info int value: " + failInfo.intValue());
        if (PKIFailureInfo.unacceptedPolicy == failInfo.intValue()) {
          LOG.debug("unaccepted policy");
        }
      }
      throw new RuntimeException(
          "timestamp response status != 0: " + timeStampResponse.getStatus());
    }
    TimeStampToken timeStampToken = timeStampResponse.getTimeStampToken();
    SignerId signerId = timeStampToken.getSID();
    BigInteger signerCertSerialNumber = signerId.getSerialNumber();
    X500Principal signerCertIssuer = signerId.getIssuer();
    LOG.debug("signer cert serial number: " + signerCertSerialNumber);
    LOG.debug("signer cert issuer: " + signerCertIssuer);

    // TSP signer certificates retrieval
    CertStore certStore =
        timeStampToken.getCertificatesAndCRLs("Collection", BouncyCastleProvider.PROVIDER_NAME);
    Collection<? extends Certificate> certificates = certStore.getCertificates(null);
    X509Certificate signerCert = null;
    Map<String, X509Certificate> certificateMap = new HashMap<String, X509Certificate>();
    for (Certificate certificate : certificates) {
      X509Certificate x509Certificate = (X509Certificate) certificate;
      if (signerCertIssuer.equals(x509Certificate.getIssuerX500Principal())
          && signerCertSerialNumber.equals(x509Certificate.getSerialNumber())) {
        signerCert = x509Certificate;
      }
      String ski = Hex.encodeHexString(getSubjectKeyId(x509Certificate));
      certificateMap.put(ski, x509Certificate);
      LOG.debug(
          "embedded certificate: " + x509Certificate.getSubjectX500Principal() + "; SKI=" + ski);
    }

    // TSP signer cert path building
    if (null == signerCert) {
      throw new RuntimeException("TSP response token has no signer certificate");
    }
    List<X509Certificate> tspCertificateChain = new LinkedList<X509Certificate>();
    X509Certificate certificate = signerCert;
    do {
      LOG.debug("adding to certificate chain: " + certificate.getSubjectX500Principal());
      tspCertificateChain.add(certificate);
      if (certificate.getSubjectX500Principal().equals(certificate.getIssuerX500Principal())) {
        break;
      }
      String aki = Hex.encodeHexString(getAuthorityKeyId(certificate));
      certificate = certificateMap.get(aki);
    } while (null != certificate);

    // verify TSP signer signature
    timeStampToken.validate(tspCertificateChain.get(0), BouncyCastleProvider.PROVIDER_NAME);

    // verify TSP signer certificate
    this.validator.validate(tspCertificateChain, revocationData);

    LOG.debug("time-stamp token time: " + timeStampToken.getTimeStampInfo().getGenTime());

    byte[] timestamp = timeStampToken.getEncoded();
    return timestamp;
  }
  public static Signature finalizeXadesT(SignedDoc sdoc, Signature sig) throws DigiDocException {
    if (m_logger.isDebugEnabled())
      m_logger.debug("Finalize XAdES-T: " + sig.getId() + " profile: " + sig.getProfile());
    UnsignedProperties usp = new UnsignedProperties(sig);
    sig.setUnsignedProperties(usp);
    if (sdoc.getFormat().equals(SignedDoc.FORMAT_BDOC)) {
      DigiDocXmlGenFactory genFac = new DigiDocXmlGenFactory(sdoc);
      TimestampFactory tsFac = ConfigManager.instance().getTimestampFactory();
      // get <SignatureValueTimeStamp>
      StringBuffer sb = new StringBuffer();
      String tsaUrl = ConfigManager.instance().getProperty("DIGIDOC_TSA_URL");
      genFac.signatureValue2xml(sb, sig.getSignatureValue(), true);
      String sSigValXml = sb.toString().trim();
      byte[] hash =
          SignedDoc.digestOfType(
              sSigValXml.getBytes(),
              (sdoc.getFormat().equals(SignedDoc.FORMAT_BDOC)
                  ? SignedDoc.SHA256_DIGEST_TYPE
                  : SignedDoc.SHA1_DIGEST_TYPE));
      if (m_logger.isDebugEnabled())
        m_logger.debug(
            "Get sig-val-ts for: "
                + Base64Util.encode(hash)
                + " uri: "
                + tsaUrl
                + " DATA:\n---\n"
                + sSigValXml
                + "\n---\n");
      TimeStampResponse tresp = tsFac.requestTimestamp(TSPAlgorithms.SHA1.getId(), hash, tsaUrl);
      if (tresp != null) {
        TimestampInfo ti =
            new TimestampInfo(
                sig.getId() + "-T0", sig, TimestampInfo.TIMESTAMP_TYPE_SIGNATURE, hash, tresp);
        ti.addIncludeInfo(new IncludeInfo("#" + sig.getId() + "-SIG"));
        sig.addTimestampInfo(ti);
        try {
          if (m_logger.isDebugEnabled())
            m_logger.debug("Timestamp: " + Base64Util.encode(tresp.getEncoded()));
        } catch (Exception ex) {
        }
        // sb = new StringBuffer();
        // genFac.timestampInfo2xml(sb, ti, true);
        // String sToXml = sb.toString();
        // TODO: add TSA refs and certs ? Not in TSL yet!
        sig.setProfile(SignedDoc.BDOC_PROFILE_T);
        try {
          X509Certificate cert =
              SignedDoc.readCertificate(
                  new java.io.File("/Users/veiko/workspace/jdigidoc/trunk/iaik-tsa.crt"));

          /*Store st = tresp.getTimeStampToken().getCertificates();
          if(st  != null) {
           SignerInformationStore  signers = st.getSignerInfos();
           Collection              c = signers.getSigners();
           Iterator                it = c.iterator();

           while (it.hasNext())
           {
               SignerInformation   signer = (SignerInformation)it.next();
               Collection          certCollection = certStore.getMatches(signer.getSID());

               Iterator              certIt = certCollection.iterator();
               X509CertificateHolder cert = (X509CertificateHolder)certIt.next();


           }
          }*/
        } catch (Exception ex) {
          m_logger.error("Error ts: " + ex);
        }
      }
    }
    return sig;
  }