All Downloads are FREE. Search and download functionalities are using the official Maven repository.

se.idsec.signservice.integration.document.pdf.PdfTbsDocumentProcessor Maven / Gradle / Ivy

/*
 * Copyright 2019-2020 IDsec Solutions AB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package se.idsec.signservice.integration.document.pdf;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.Base64;

import org.apache.commons.lang.StringUtils;
import org.apache.pdfbox.pdmodel.PDDocument;

import lombok.extern.slf4j.Slf4j;
import se.idsec.signservice.integration.SignRequestInput;
import se.idsec.signservice.integration.config.IntegrationServiceConfiguration;
import se.idsec.signservice.integration.core.error.ErrorCode;
import se.idsec.signservice.integration.core.error.InputValidationException;
import se.idsec.signservice.integration.core.impl.CorrelationID;
import se.idsec.signservice.integration.core.validation.ValidationResult;
import se.idsec.signservice.integration.document.DocumentDecoder;
import se.idsec.signservice.integration.document.DocumentEncoder;
import se.idsec.signservice.integration.document.DocumentProcessingException;
import se.idsec.signservice.integration.document.DocumentType;
import se.idsec.signservice.integration.document.ProcessedTbsDocument;
import se.idsec.signservice.integration.document.TbsDocument;
import se.idsec.signservice.integration.document.TbsDocument.EtsiAdesRequirement;
import se.idsec.signservice.integration.document.impl.AbstractTbsDocumentProcessor;
import se.idsec.signservice.integration.document.impl.EtsiAdesRequirementValidator;
import se.idsec.signservice.integration.document.impl.TbsCalculationResult;
import se.idsec.signservice.integration.document.pdf.utils.PDFIntegrationUtils;
import se.idsec.signservice.integration.document.pdf.visiblesig.VisiblePdfSignatureRequirementException;
import se.idsec.signservice.integration.document.pdf.visiblesig.VisibleSignatureImageFactory;
import se.idsec.signservice.integration.document.pdf.visiblesig.VisibleSignatureImageSerializer;
import se.idsec.signservice.security.sign.AdesProfileType;
import se.idsec.signservice.security.sign.impl.StaticCredentials;
import se.idsec.signservice.security.sign.pdf.PDFSignerParameters;
import se.idsec.signservice.security.sign.pdf.PDFSignerResult;
import se.idsec.signservice.security.sign.pdf.impl.DefaultPDFSigner;

/**
 * PDF TBS-document processor.
 *
 * @author Martin Lindström ([email protected])
 * @author Stefan Santesson ([email protected])
 */
@Slf4j
public class PdfTbsDocumentProcessor extends AbstractTbsDocumentProcessor {

  /** We need to use dummy keys when creating the to-be-signed bytes. */
  private final StaticCredentials staticKeys = new StaticCredentials();

  /** Validator for visible PDF signature requirements. */
  protected final VisiblePdfSignatureRequirementValidator visiblePdfSignatureRequirementValidator = new VisiblePdfSignatureRequirementValidator();

  /** Document decoder. */
  protected final static PdfDocumentEncoderDecoder documentEncoderDecoder = new PdfDocumentEncoderDecoder();

  /** {@inheritDoc} */
  @Override
  public boolean supports(final TbsDocument document) {
    try {
      return DocumentType.fromMimeType(document.getMimeType()) == DocumentType.PDF;
    }
    catch (IllegalArgumentException e) {
      return false;
    }
  }

  /**
   * Handles settings for PDF visible signatures.
   */
  @Override
  public ProcessedTbsDocument preProcess(final TbsDocument document, final SignRequestInput signRequestInput,
      final IntegrationServiceConfiguration config, final String fieldName) throws InputValidationException {

    final ProcessedTbsDocument processedTbsDocument = super.preProcess(document, signRequestInput, config, fieldName);
    final TbsDocument tbsDocument = processedTbsDocument.getTbsDocument();

    if (tbsDocument.getVisiblePdfSignatureRequirement() == null) {
      if (config.getDefaultVisiblePdfSignatureRequirement() != null) {
        log.debug("{}: Setting default value for visiblePdfSignatureRequirement ({}): {}",
          CorrelationID.id(), tbsDocument.getId(), config.getDefaultVisiblePdfSignatureRequirement());
        tbsDocument.setVisiblePdfSignatureRequirement(config.getDefaultVisiblePdfSignatureRequirement());
      }
    }
    else {

      // Is this a "null" request. If so, remove it ...
      //
      if (Boolean.valueOf(tbsDocument.getVisiblePdfSignatureRequirement()
        .getExtensionValue(VisiblePdfSignatureRequirement.NULL_INDICATOR_EXTENSION))) {

        log.debug("{}: Document '{}' contains a null requirement, removing ...",
          CorrelationID.id(), tbsDocument.getId());
        tbsDocument.setVisiblePdfSignatureRequirement(null);
      }
      else {
        // Validate the input ...
        //
        this.visiblePdfSignatureRequirementValidator.validateObject(
          tbsDocument.getVisiblePdfSignatureRequirement(), fieldName + ".visiblePdfSignatureRequirement", config);

        // Scale ...
        //
        if (tbsDocument.getVisiblePdfSignatureRequirement().getScale() == null) {
          log.info("{}: visiblePdfSignatureRequirement.scale is not set, defaulting to 0", CorrelationID.id());
          tbsDocument.getVisiblePdfSignatureRequirement().setScale(0);
        }
        else if (tbsDocument.getVisiblePdfSignatureRequirement().getScale().intValue() < -100) {
          log.info("{}: visiblePdfSignatureRequirement.scale is set to '{}'. This is illegal, changing to -100",
            CorrelationID.id(), tbsDocument.getVisiblePdfSignatureRequirement().getScale());
          tbsDocument.getVisiblePdfSignatureRequirement().setScale(-100);
        }

        // Page ...
        //
        if (tbsDocument.getVisiblePdfSignatureRequirement().getPage() == null) {
          log.info("{}: visiblePdfSignatureRequirement.page is not set, defaulting to 0", CorrelationID.id());
          tbsDocument.getVisiblePdfSignatureRequirement().setPage(0);
        }
        if (tbsDocument.getVisiblePdfSignatureRequirement().getPage().intValue() < 0) {
          log.info("{}: visiblePdfSignatureRequirement.page is set to '{}'. This is illegal, changing to 0",
            CorrelationID.id(), tbsDocument.getVisiblePdfSignatureRequirement().getPage());
          tbsDocument.getVisiblePdfSignatureRequirement().setPage(0);
        }
      }
    }

    // AdES requirements ...
    //
    final EtsiAdesRequirement requestedAdes = tbsDocument.getAdesRequirement();
    final AdesProfileType ades = PDFIntegrationUtils.getPadesRequirement(requestedAdes);
    tbsDocument.addExtensionValue(PDFExtensionParams.adesRequirement.name(), ades.getStringValue());

    // Visible PDF signature requirements ...
    //
    final VisiblePdfSignatureRequirement visiblePdfSignatureRequirement = tbsDocument.getVisiblePdfSignatureRequirement();
    if (visiblePdfSignatureRequirement != null) {
      final VisibleSignatureImageFactory factory = new VisibleSignatureImageFactory(config.getPdfSignatureImageTemplates());
      try {
        final String encodedVisibleSignImage = factory.getEncodedVisibleSignImage(
          visiblePdfSignatureRequirement, signRequestInput.getAuthnRequirements().getRequestedSignerAttributes());
        tbsDocument.addExtensionValue(PDFExtensionParams.visibleSignImage.name(), encodedVisibleSignImage);
      }
      catch (VisiblePdfSignatureRequirementException e) {
        throw new InputValidationException(fieldName + ".visiblePdfSignatureRequirement", e.getMessage(), e);
      }
    }

    return processedTbsDocument;
  }

  /** {@inheritDoc} */
  @Override
  protected TbsCalculationResult calculateToBeSigned(final ProcessedTbsDocument document, final String signatureAlgorithm,
      IntegrationServiceConfiguration config) throws DocumentProcessingException {

    final TbsDocument tbsDocument = document.getTbsDocument();

    // Sign the document using a fake key - in order to obtain the to-be-signed bytes.
    //
    try {
      final DefaultPDFSigner signer = new DefaultPDFSigner(staticKeys.getSigningCredential(signatureAlgorithm), signatureAlgorithm);
      signer.setIncludeCertificateChain(false);

      final String padesString = tbsDocument.getExtensionValue(PDFExtensionParams.adesRequirement.name());
      final String encodedVisibleImage = tbsDocument.getExtensionValue(PDFExtensionParams.visibleSignImage.name());

      final PDFSignerParameters signerParameters = PDFSignerParameters.builder()
        .padesType(padesString != null ? AdesProfileType.fromStringValue(padesString) : null)
        .visibleSignatureImage(
          encodedVisibleImage != null ? VisibleSignatureImageSerializer.deserializeVisibleSignImage(encodedVisibleImage) : null)
        .build();

      final PDFSignerResult pdfSignerResult = signer.sign(document.getDocumentObject(byte[].class), signerParameters);

      TbsCalculationResult tbsResult = TbsCalculationResult.builder()
        .toBeSignedBytes(pdfSignerResult.getSignedAttributes())
        .sigType("PDF")
        .build();

      // Set extensions in TbsDocument with the PDF signature ID and time
      //
      tbsDocument.addExtensionValue(PDFExtensionParams.signTimeAndId.name(), String.valueOf(pdfSignerResult.getSigningTime()));
      tbsDocument.addExtensionValue(PDFExtensionParams.cmsSignedData.name(),
        Base64.getEncoder().encodeToString(pdfSignerResult.getSignedData()));

      return tbsResult;
    }
    catch (NoSuchAlgorithmException | IllegalArgumentException | IOException | SignatureException e) {
      final String msg = String.format("Error while calculating signed attributes for PDF document '%s' - %s", tbsDocument.getId(), e
        .getMessage());
      log.error("{}: {}", CorrelationID.id(), msg, e);
      throw new DocumentProcessingException(new ErrorCode.Code("sign"), msg, e);
    }
  }

  /**
   * Overrides the default implementation and ensures that the bytes that makes up the PDF document really are OK, that
   * is, we ensure that they can be loaded into a {@link PDDocument}.
   */
  @Override
  protected byte[] validateDocumentContent(final TbsDocument document, final IntegrationServiceConfiguration config, final String fieldName)
      throws InputValidationException {

    final byte[] pdfDocumentBytes = super.validateDocumentContent(document, config, fieldName);
    try {
      InputStream is = new ByteArrayInputStream(pdfDocumentBytes);
      PDDocument pdfDocument = PDDocument.load(is);
      pdfDocument.close();
    }
    catch (Exception e) {
      final String msg = String.format("Failed to load content for document '%s' - %s", document.getId(), e.getMessage());
      log.error("{}: {}", CorrelationID.id(), msg, e);
      throw new InputValidationException(fieldName + ".content", msg, e);
    }
    return pdfDocumentBytes;
  }

  /** {@inheritDoc} */
  @Override
  public DocumentDecoder getDocumentDecoder() {
    return documentEncoderDecoder;
  }

  /** {@inheritDoc} */
  @Override
  public DocumentEncoder getDocumentEncoder() {
    return documentEncoderDecoder;
  }

  /** {@inheritDoc} */
  @Override
  protected EtsiAdesRequirementValidator getEtsiAdesRequirementValidator() {
    return new PadesRequirementValidator();
  }

  /**
   * Validator for {@link EtsiAdesRequirement} objects.
   */
  public static class PadesRequirementValidator extends EtsiAdesRequirementValidator {

    /** {@inheritDoc} */
    @Override
    public ValidationResult validate(EtsiAdesRequirement object, String objectName, Void hint) {
      ValidationResult result = new ValidationResult(objectName);
      if (object == null) {
        return result;
      }

      if (TbsDocument.AdesType.EPES.equals(object.getAdesFormat()) && StringUtils.isBlank(object.getSignaturePolicy())) {
        result.rejectValue("signaturePolicy",
          "AdES requirement states Extended Policy Electronic Signature but no signature policy has been given");
      }

      return result;
    }

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy