//========================================================================
//
// pdfsig.cc
//
// This file is licensed under the GPLv2 or later
//
// Copyright 2015 André Guerreiro <aguerreiro1985@gmail.com>
// Copyright 2015 André Esser <bepandre@hotmail.com>
// Copyright 2015, 2017-2020 Albert Astals Cid <aacid@kde.org>
// Copyright 2016 Markus Kilås <digital@markuspage.com>
// Copyright 2017, 2019 Hans-Ulrich Jüttner <huj@froreich-bioscientia.de>
// Copyright 2017, 2019 Adrian Johnson <ajohnson@redneon.com>
// Copyright 2018 Chinmoy Ranjan Pradhan <chinmoyrp65@protonmail.com>
// Copyright 2019 Alexey Pavlov <alexpux@gmail.com>
// Copyright 2019 Oliver Sander <oliver.sander@tu-dresden.de>
// Copyright 2019 Nelson Efrain A. Cruz <neac03@gmail.com>
//
//========================================================================

#include "config.h"
#include <poppler-config.h>
#include <cstdio>
#include <cstdlib>
#include <cstddef>
#include <cstring>
#include <ctime>
#include <hasht.h>
#include <fstream>
#include "parseargs.h"
#include "Object.h"
#include "Array.h"
#include "goo/gbasename.h"
#include "Page.h"
#include "PDFDoc.h"
#include "PDFDocFactory.h"
#include "Error.h"
#include "GlobalParams.h"
#include "SignatureHandler.h"
#include "SignatureInfo.h"
#include "Win32Console.h"
#include "numberofcharacters.h"
#include <libgen.h>

static const char * getReadableSigState(SignatureValidationStatus sig_vs)
{
  switch(sig_vs) {
    case SIGNATURE_VALID:
      return "Signature is Valid.";

    case SIGNATURE_INVALID:
      return "Signature is Invalid.";

    case SIGNATURE_DIGEST_MISMATCH:
      return "Digest Mismatch.";

    case SIGNATURE_DECODING_ERROR:
      return "Document isn't signed or corrupted data.";

    case SIGNATURE_NOT_VERIFIED:
      return "Signature has not yet been verified.";

    default:
      return "Unknown Validation Failure.";
  }
}

static const char * getReadableCertState(CertificateValidationStatus cert_vs)
{
  switch(cert_vs) {
    case CERTIFICATE_TRUSTED:
      return "Certificate is Trusted.";

    case CERTIFICATE_UNTRUSTED_ISSUER:
      return "Certificate issuer isn't Trusted.";

    case CERTIFICATE_UNKNOWN_ISSUER:
      return "Certificate issuer is unknown.";

    case CERTIFICATE_REVOKED:
      return "Certificate has been Revoked.";

    case CERTIFICATE_EXPIRED:
      return "Certificate has Expired";

    case CERTIFICATE_NOT_VERIFIED:
      return "Certificate has not yet been verified.";

    default:
      return "Unknown issue with Certificate or corrupted data.";
  }
}

static char *getReadableTime(time_t unix_time)
{
  char * time_str = (char *) gmalloc(64);
  strftime(time_str, 64, "%b %d %Y %H:%M:%S", localtime(&unix_time));
  return time_str;
}

static bool dumpSignature(int sig_num, int sigCount, FormFieldSignature *s, const char *filename)
{
    const GooString *signature = s->getSignature();
    if (!signature) {
        printf("Cannot dump signature #%d\n", sig_num);
        return false;
    }

    const int sigCountLength = numberOfCharacters(sigCount);
    // We want format to be {0:s}.sig{1:Xd} where X is sigCountLength
    // since { is the magic character to replace things we need to put it twice where
    // we don't want it to be replaced
    GooString *format = GooString::format("{{0:s}}.sig{{1:{0:d}d}}", sigCountLength);
    GooString *path = GooString::format(format->c_str(), gbasename(filename).c_str(), sig_num);
    printf("Signature #%d (%u bytes) => %s\n", sig_num, signature->getLength(), path->c_str());
    std::ofstream outfile(path->c_str(), std::ofstream::binary);
    outfile.write(signature->c_str(), signature->getLength());
    outfile.close();
    delete format;
    delete path;

    return true;
}

static GooString nssDir;
static bool printVersion = false;
static bool printHelp = false;
static bool dontVerifyCert = false;
static bool dumpSignatures = false;

static const ArgDesc argDesc[] = {
  {"-nssdir", argGooString, &nssDir,     0,
   "path to directory of libnss3 database"},
  {"-nocert", argFlag,     &dontVerifyCert,     0,
   "don't perform certificate validation"},
  {"-dump",   argFlag,     &dumpSignatures,     0,
   "dump all signatures into current directory"},

  {"-v",      argFlag,     &printVersion,  0,
   "print copyright and version info"},
  {"-h",      argFlag,     &printHelp,     0,
   "print usage information"},
  {"-help",   argFlag,     &printHelp,     0,
   "print usage information"},
  {"-?",      argFlag,     &printHelp,     0,
   "print usage information"},
  {}
};


int main(int argc, char *argv[])
{
  char *time_str = nullptr;
  globalParams = std::make_unique<GlobalParams>();

  Win32Console win32Console(&argc, &argv);

  const bool ok = parseArgs(argDesc, &argc, argv);

  if (!ok || argc != 2 || printVersion || printHelp) {
    fprintf(stderr, "pdfsig version %s\n", PACKAGE_VERSION);
    fprintf(stderr, "%s\n", popplerCopyright);
    fprintf(stderr, "%s\n", xpdfCopyright);
    if (!printVersion) {
      printUsage("pdfsig", "<PDF-file>", argDesc);
    }
    if (printVersion || printHelp)
      return 0;
    return 99;
  }

  std::unique_ptr<GooString> fileName = std::make_unique<GooString>(argv[argc - 1]);

  SignatureHandler::setNSSDir(nssDir);

  // open PDF file
  std::unique_ptr<PDFDoc> doc(PDFDocFactory().createPDFDoc(*fileName, nullptr, nullptr));

  if (!doc->isOk()) {
    return 1;
  }

  const std::vector<FormFieldSignature*> signatures = doc->getSignatureFields();
  const unsigned int sigCount = signatures.size();

  if (sigCount >= 1) {
    if (dumpSignatures) {
      printf("Dumping Signatures: %u\n", sigCount);
      for (unsigned int i = 0; i < sigCount; i++) {
        const bool dumpingOk = dumpSignature(i, sigCount, signatures.at(i), fileName->c_str());
        if (!dumpingOk) {
	  return 3;
        }
      }
      return 0;
    } else {
      printf("Digital Signature Info of: %s\n", fileName->c_str());
    }
  } else {
    printf("File '%s' does not contain any signatures\n", fileName->c_str());
    return 2;
  }

  for (unsigned int i = 0; i < sigCount; i++) {
    const SignatureInfo *sig_info = signatures.at(i)->validateSignature(!dontVerifyCert, false, -1 /* now */);
    printf("Signature #%u:\n", i+1);
    printf("  - Signer Certificate Common Name: %s\n", sig_info->getSignerName());
    printf("  - Signer full Distinguished Name: %s\n", sig_info->getSubjectDN());
    printf("  - Signing Time: %s\n", time_str = getReadableTime(sig_info->getSigningTime()));
    printf("  - Signing Hash Algorithm: ");
    switch (sig_info->getHashAlgorithm())
    {
      case HASH_AlgMD2:
        printf("MD2\n");
        break;
      case HASH_AlgMD5:
        printf("MD5\n");
        break;
      case HASH_AlgSHA1:
        printf("SHA1\n");
        break;
      case HASH_AlgSHA256:
        printf("SHA-256\n");
        break;
      case HASH_AlgSHA384:
        printf("SHA-384\n");
        break;
      case HASH_AlgSHA512:
        printf("SHA-512\n");
        break;
      case HASH_AlgSHA224:
        printf("SHA-224\n");
        break;
      default:
        printf("unknown\n");
    }
    printf("  - Signature Type: ");
    switch (signatures.at(i)->getSignatureType())
    {
      case adbe_pkcs7_sha1:
        printf("adbe.pkcs7.sha1\n");
        break;
      case adbe_pkcs7_detached:
        printf("adbe.pkcs7.detached\n");
        break;
      case ETSI_CAdES_detached:
        printf("ETSI.CAdES.detached\n");
        break;
      default:
        printf("unknown\n");
    }
    std::vector<Goffset> ranges = signatures.at(i)->getSignedRangeBounds();
    if (ranges.size() == 4)
    {
      printf("  - Signed Ranges: [%lld - %lld], [%lld - %lld]\n",
             ranges[0], ranges[1], ranges[2], ranges[3]);
      Goffset checked_file_size;
      GooString* signature = signatures.at(i)->getCheckedSignature(&checked_file_size);
      if (signature && checked_file_size == ranges[3]) {
        printf("  - Total document signed\n");
      } else {
        printf("  - Not total document signed\n");
      }
      delete signature;
    }
    printf("  - Signature Validation: %s\n", getReadableSigState(sig_info->getSignatureValStatus()));
    gfree(time_str);
    if (sig_info->getSignatureValStatus() != SIGNATURE_VALID || dontVerifyCert) {
      continue;
    }
    printf("  - Certificate Validation: %s\n", getReadableCertState(sig_info->getCertificateValStatus()));
  }

  return 0;
}
