/**
   * Prueba que el sistema de firma masiva programatica no se queda colgado con ficheros mayores de
   * 7 megas.
   *
   * @throws Exception Cuando se produce algún error durante la prueba.
   */
  @SuppressWarnings("static-method")
  @Test
  public void pruebaComprobacionPasoBase64() throws Exception {

    final byte[] data = getResource(DATA_FILE);

    Assert.assertFalse(AOUtil.isBase64(data));
    Assert.assertTrue(AOUtil.isBase64(Base64.encode(data).getBytes()));
  }
  private static String createSingleReqPresignNode(final TriphaseRequest triphaseRequest) {
    final StringBuilder sb = new StringBuilder("<req id=\""); // $NON-NLS-1$
    sb.append(triphaseRequest.getRef());
    sb.append("\" status=\""); // $NON-NLS-1$
    if (triphaseRequest.isStatusOk()) {
      sb.append("OK\">"); // $NON-NLS-1$
      for (final TriphaseSignDocumentRequest docReq : triphaseRequest) {
        sb.append("<doc docid=\"")
            .append(docReq.getId()) // $NON-NLS-1$
            .append("\" cop=\"")
            .append(docReq.getCryptoOperation()) // $NON-NLS-1$
            .append("\" sigfrmt=\"")
            .append(docReq.getSignatureFormat()) // $NON-NLS-1$
            .append("\" mdalgo=\"")
            .append(docReq.getMessageDigestAlgorithm())
            .append("\">") // $NON-NLS-1$ //$NON-NLS-2$
            .append("<params>")
            .append(docReq.getParams() != null ? docReq.getParams() : "")
            .append("</params>") // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            .append("<result>") // $NON-NLS-1$
            .append(docReq.getPartialResult().toXMLConfig())
            .append("</result></doc>"); // $NON-NLS-1$
      }
      sb.append("</req>"); // $NON-NLS-1$
    } else {

      String exceptionb64 = null;
      final Throwable t = triphaseRequest.getThrowable();
      if (t != null) {
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        t.printStackTrace(new PrintWriter(baos));
        exceptionb64 = Base64.encode(baos.toByteArray());
        try {
          baos.close();
        } catch (IOException e) {
          /* No hacemos nada */
        }
      }

      if (exceptionb64 != null) {
        sb.append("KO\" exceptionb64=\"") // $NON-NLS-1$
            .append(exceptionb64)
            .append("\" />"); // $NON-NLS-1$
      } else {
        sb.append("KO\" />"); // $NON-NLS-1$
      }
    }
    return sb.toString();
  }
  /**
   * Main.
   *
   * @param args
   * @throws Exception
   */
  public static void main(final String[] args) throws Exception {

    // PDF de ejemplo
    final PdfReader reader =
        new PdfReader(
            AOUtil.getDataFromInputStream(
                ClassLoader.getSystemResourceAsStream("TEST_PDF.pdf") // $NON-NLS-1$
                ));
    final ByteArrayOutputStream baos = new ByteArrayOutputStream();
    final Calendar globalDate = new GregorianCalendar();
    final PdfStamper stamper = new PdfStamper(reader, baos, globalDate);

    //		// Datos a insertar como XMP
    //		final BioMetadataSchema schema = new BioMetadataSchema();
    //		schema.addIso197947Data("HOLA".getBytes()); //$NON-NLS-1$
    //
    //		// Insertamos los datos en el XMP
    //		final ByteArrayOutputStream os = new ByteArrayOutputStream();
    //        final XmpWriter xmp = new XmpWriter(os);
    //        xmp.addRdfDescription(schema);
    //        xmp.close();
    //
    //        // Insertamos el XMP en el PDF
    //        stamper.setXmpMetadata(os.toByteArray());

    final String sigDataBase64 =
        Base64.encode(
            AOUtil.getDataFromInputStream(
                ClassLoader.getSystemResourceAsStream("4df6ec6b6b5c7.jpg") // $NON-NLS-1$
                ));
    final HashMap<String, String> moreInfo = new HashMap<String, String>(1);
    moreInfo.put("SignerBiometricSignatureData", sigDataBase64); // $NON-NLS-1$
    moreInfo.put("SignerBiometricSignatureFormat", "ISO 19795-7"); // $NON-NLS-1$ //$NON-NLS-2$
    moreInfo.put("SignerName", "Tom\u00E1s Garc\u00EDa-Mer\u00E1s"); // $NON-NLS-1$ //$NON-NLS-2$
    stamper.setMoreInfo(moreInfo);

    stamper.close(globalDate);
    reader.close();

    // Guardamos el resultado
    final File tmpFile = File.createTempFile("TESTXMP_", ".pdf"); // $NON-NLS-1$ //$NON-NLS-2$
    final OutputStream fos = new FileOutputStream(tmpFile);
    fos.write(baos.toByteArray());
    fos.flush();
    fos.close();
  }
  /**
   * Prefirma (firma simple) en formato XAdES.
   *
   * @param data Datos a prefirmar
   * @param algorithm Algoritmo de firma
   * @param certChain Cadena de certificados del firmante
   * @param xParams Par&aacute;metros adicionales de la firma
   * @param op Operaci&oacute;n espec&iacute;fica de firma a realizar
   * @return Listado de prefirma XML
   * @throws NoSuchAlgorithmException Si el JRE no soporta alg&uacute;n algoritmo necesario.
   * @throws AOException Si ocurre cualquier otro error.
   * @throws SAXException Si hay problemas en el an&aacute;lisis XML.
   * @throws IOException Si hay problemas en el tratamiento de datos.
   * @throws ParserConfigurationException Si hay problemas con el analizador por defecto de XML.
   * @throws MarshalException Si hay problemas con el empaquetado XML.
   * @throws XMLSignatureException Si hay problemas en la propia firma XMLDSig.
   * @throws InvalidKeyException Si la clave proporcinoada no es v&aacute;lida.
   * @throws SignatureException Si hay problemas con la firma PKCS#1.
   * @throws XmlPreSignException Si hay un error en la pre-firma XAdES.
   */
  public static XmlPreSignResult preSign(
      final byte[] data,
      final String algorithm,
      final Certificate[] certChain,
      final Properties xParams,
      final Op op)
      throws NoSuchAlgorithmException, AOException, SAXException, IOException,
          ParserConfigurationException, MarshalException, XMLSignatureException,
          InvalidKeyException, SignatureException, XmlPreSignException {
    if (data == null || data.length < 1) {
      throw new IllegalArgumentException(
          "Los datos a prefirmar no pueden ser nulos ni vacios"); //$NON-NLS-1$
    }
    if (algorithm == null || "".equals(algorithm)) { // $NON-NLS-1$
      throw new IllegalArgumentException(
          "El algoritmo de firma no puede ser nulo ni vacio"); //$NON-NLS-1$
    }
    if (certChain == null || certChain.length < 1) {
      throw new IllegalArgumentException(
          "La cadena de certificados no puede ser nula y debe contener al menos un certificado" //$NON-NLS-1$
          );
    }

    // Miramos si la entrada es un XML que ya contenga firmas (almacenando los ID para luego
    // localizar cual es la firma
    // nueva y no confundirla con las antiguas), y si lo es cual es su codificacion con un
    // Document.getXmlEncoding()
    final List<String> previousSignaturesIds = new ArrayList<String>();
    Document xml = null;
    String xmlEncoding = XML_DEFAULT_ENCODING;
    try {
      xml =
          DocumentBuilderFactory.newInstance()
              .newDocumentBuilder()
              .parse(new ByteArrayInputStream(data));
      if (xml.getXmlEncoding() != null) {
        xmlEncoding = xml.getXmlEncoding();
      }
    } catch (final Exception e) {
      Logger.getLogger("es.gob.afirma")
          .info(
              "El documento a firmar no es XML, por lo que no contiene firmas previas"); //$NON-NLS-1$//$NON-NLS-2$
    }

    if (xml == null && (op == Op.COSIGN || op == Op.COUNTERSIGN)) {
      Logger.getLogger("es.gob.afirma")
          .severe("Solo se pueden cofirmar y contrafirmar firmas XML"); // $NON-NLS-1$//$NON-NLS-2$
      throw new AOException(
          "Los datos introducidos no se corresponden con una firma XML"); //$NON-NLS-1$
    }

    // Si los datos eran XML, comprobamos y almacenamos las firmas previas
    if (xml != null) {
      final Element rootElement = xml.getDocumentElement();
      if (rootElement.getNodeName().endsWith(":" + AOXAdESSigner.SIGNATURE_TAG)) { // $NON-NLS-1$
        final NamedNodeMap nnm = rootElement.getAttributes();
        if (nnm != null) {
          final Node node = nnm.getNamedItem(XML_NODE_ID);
          if (node != null) {
            final String id = node.getNodeValue();
            if (id != null) {
              previousSignaturesIds.add(id);
            }
          }
        }
      } else {
        final NodeList mainChildNodes = xml.getDocumentElement().getChildNodes();
        for (int i = 0; i < mainChildNodes.getLength(); i++) {
          final Node currentNode = mainChildNodes.item(i);
          if (currentNode.getNodeType() == Node.ELEMENT_NODE
              && currentNode
                  .getNodeName()
                  .endsWith(":" + AOXAdESSigner.SIGNATURE_TAG)) { // $NON-NLS-1$
            final NamedNodeMap nnm = currentNode.getAttributes();
            if (nnm != null) {
              final Node node = nnm.getNamedItem(XML_NODE_ID);
              if (node != null) {
                final String id = node.getNodeValue();
                if (id != null) {
                  previousSignaturesIds.add(id);
                }
              }
            }
          }
        }
      }
    }

    // Generamos un par de claves para hacer la firma temporal, que despues sustituiremos por la
    // real
    final RSAPrivateKey prk =
        (RSAPrivateKey)
            generateKeyPair(
                    ((RSAPublicKey) ((X509Certificate) certChain[0]).getPublicKey())
                        .getModulus()
                        .bitLength())
                .getPrivate();

    final byte[] result;
    switch (op) {
      case SIGN:
        result = XAdESSigner.sign(data, algorithm, prk, certChain, xParams);
        break;
      case COSIGN:
        result = XAdESCoSigner.cosign(data, algorithm, prk, certChain, xParams);
        break;
      case COUNTERSIGN:
        final CounterSignTarget targets =
            xParams != null
                    && CounterSignTarget.LEAFS
                        .name()
                        .equalsIgnoreCase(xParams.getProperty(COUNTERSIGN_TARGET_KEY))
                ? CounterSignTarget.LEAFS
                : CounterSignTarget.TREE;

        result =
            XAdESCounterSigner.countersign(data, algorithm, targets, null, prk, certChain, xParams);
        break;
      default:
        throw new IllegalStateException(
            "No se puede dar una operacion no contemplada en el enumerado de operaciones" //$NON-NLS-1$
            );
    }

    // Cargamos el XML firmado en un String
    String xmlResult = new String(result, xmlEncoding);

    // Recuperamos los signed info que se han firmado
    final List<byte[]> signedInfos =
        XAdESTriPhaseSignerServerSide.getSignedInfos(
            result,
            certChain[0].getPublicKey(),
            previousSignaturesIds // Identificadores de firmas previas, para poder omitirlos
            );

    // Ponemos un reemplazo en el XML en lugar de los PKCS#1 de las firmas generadas
    for (int i = 0; i < signedInfos.size(); i++) {

      final byte[] signedInfo = signedInfos.get(i);

      // Calculamos el valor PKCS#1 con la clave privada impostada, para conocer los valores que
      // debemos sustituir
      final Signature signature = Signature.getInstance(algorithm);
      signature.initSign(prk);
      signature.update(signedInfo);

      final String cleanSignatureValue = cleanBase64(Base64.encode(signature.sign()));

      // Buscamos el PKCS#1 en Base64 en el XML original y lo sustituimos por la cadena de reemplazo
      final String signValuePrefix =
          ">" + cleanSignatureValue.substring(0, NUM_CHARACTERS_TO_COMPARE); // $NON-NLS-1$
      final int signValuePos = xmlResult.indexOf(signValuePrefix) + 1;
      final int signValueFinalPos = xmlResult.indexOf('<', signValuePos);
      final String pkcs1String = xmlResult.substring(signValuePos, signValueFinalPos);

      final String cleanPkcs1String = cleanBase64(pkcs1String);
      if (cleanSignatureValue.equals(cleanPkcs1String)) {
        xmlResult =
            xmlResult.replace(
                pkcs1String, REPLACEMENT_STRING.replace(REPLACEMENT_CODE, Integer.toString(i)));
      }
    }
    return new XmlPreSignResult(xmlResult.getBytes(xmlEncoding), signedInfos);
  }