PadesLevels.cs
//
// This code is part of Document Solutions for PDF demos.
// Copyright (c) MESCIUS inc. All rights reserved.
//
using System;
using System.IO;
using System.Drawing;
using System.Text;
using System.Linq;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

using GrapeCity.Documents.Pdf;
using GrapeCity.Documents.Pdf.Security;
using GrapeCity.Documents.Pdf.AcroForms;
using GrapeCity.Documents.Text;


namespace DsPdfWeb.Demos
{
    // This sample shows how to sign an existing PDF file containing
    // an empty signature field with a signature that complies with
    // the various PAdES (PDF Advanced Electronic Signatures) levels.
    // Note that when run online, this sample simply returns an unsigned
    // PDF with an empty signature field. To actually sign the PDF
    // you will need to provide a valid .pfx file (see "JohnDoe.pfx").
    // The sample contains 5 methods that can be used to sign a PDF
    // with the various PAdES levels, the last 3 methods can be
    // used in a chain to incrementally increase the signature strength.
    // To check the signature compliance, open the signed PDF in Acrobat
    // Reader and inspect the signature properties.
    //
    // IMPORTANT! The code in this sample WILL NOT work correctly
    // unless DsPdf is licensed with a valid license key.
    // Email us.sales@mescius.com to obtain a trial key.
    public class PadesLevels
    {
        // Certificate issued by CA Cert Signing Authority, used for verification:
        static X509Certificate2 s_caCertRoot = new X509Certificate2(Path.Combine("Resources", "Misc", "CACertRoot.crt"));
        // TODO: replace with a real certificate:
        static X509Certificate2 s_cert = new X509Certificate2(Path.Combine("Resources", "Misc", "JohnDoe.pfx"), "secret");

        static PadesLevels()
        {
            // TODO: GcPdfDocument MUST BE LICENSED for the signatures to remain valid,
            // as otherwise the license nag text added when saving the PDF invalidates
            // incremental updates. Email us.sales@mescius.com to obtain a trial key.
            // GcPdfDocument.SetLicenseKey("my key");
        }

        public void CreatePDF(Stream stream, int paramsIdx = 0)
        {
            CreatePDF(stream, GetSampleParamsList()[paramsIdx]);
        }

        // While this sample includes code that does the actual signing and time stamping,
        // that code is NOT executed by the online demo. So the online sample driver
        // simply returns the PDF that includes info about the selected PAdES level,
        // and can be signed or time stamped using a valid certificate.
        // You can copy the corresponding code and use it in your applications.
        public void CreatePDF(Stream stream, string[] sampleParams)
        {
            // TODO: change to true to actually run the signing/time stamping code:
            bool doSigning = false;

            // The unsigned PDF with a signature field to sign:
            var fn = Path.Combine("Resources", "PDFs", sampleParams[3]);

            if (doSigning)
            {
                // Running this code will produce 5 PDFs with increasing PAdES levels:

                // PAdES B-B:
                Do_B_B(fn);
                // PAdES B-T:
                Do_B_T(fn);
                // Note that the next 3 steps use PDFs produced by the previous step,
                // incrementally adding verification info:
                // PAdES B-LT:
                Do_B_LT("B-T.pdf");
                // PAdES B-LTA:
                Do_B_LTA("B-LT.pdf");
                // LTV Enabled:
                Do_B_LTA_LTV("B-LTA.pdf");
            }

            // Copy the source PDF to output stream
            // to provide the demo result:
            using var fs = File.OpenRead(fn);
            fs.CopyTo(stream);
        }

        // Signs a PDF with a PAdES B-B Level signature:
        void Do_B_B(string fn)
        {
            using FileStream fs = File.OpenRead(fn);
            var doc = new GcPdfDocument();
            doc.Load(fs);

            var signField = doc.AcroForm.Fields.FirstOrDefault(f_ => f_ is SignatureField);
            if (signField == null)
                throw new Exception("Could not find a signature field.");

            var sp = new SignatureProperties()
            {
                SignatureField = signField,
                SignatureBuilder = new Pkcs7SignatureBuilder(s_cert)
                {
                    Format = Pkcs7SignatureBuilder.SignatureFormat.ETSI_CAdES_detached,
                },
            };
            // Sign and save the PDF to a file:
            doc.Sign(sp, "B-B.pdf");
        }

        // Signs a PDF with a PAdES B-T Level signature:
        void Do_B_T(string fn)
        {
            using FileStream fs = File.OpenRead(fn);
            var doc = new GcPdfDocument();
            doc.Load(fs);

            var signField = doc.AcroForm.Fields.FirstOrDefault(f_ => f_ is SignatureField);
            if (signField == null)
                throw new Exception("Could not find a signature field.");

            var sp = new SignatureProperties()
            {
                SignatureField = signField,
                SignatureBuilder = new Pkcs7SignatureBuilder(s_cert)
                {
                    Format = Pkcs7SignatureBuilder.SignatureFormat.ETSI_CAdES_detached,
                },
                TimeStamp = new TimeStamp(@"http://ts.ssl.com"),
            };
            // Sign and save the PDF to a file:
            doc.Sign(sp, "B-T.pdf");
        }

        // Adds LTV information to a B-T level signature (e.g. as created by the do_B_T method above),
        // which makes the signature compliant with PAdES B-LT:
        void Do_B_LT(string fn)
        {
            using FileStream fs = File.OpenRead(fn);
            var doc = new GcPdfDocument();
            doc.Load(fs);

            var signField = doc.AcroForm.Fields.FirstOrDefault(f_ => f_ is SignatureField);
            if (signField == null)
                throw new Exception("Could not find a signature field.");

            // Get the signature and add verification information for it:
            var sig = (Signature)signField.Value;
            var vp = new DocumentSecurityStore.VerificationParams();
            vp.Certificates = new X509Certificate2[] { s_caCertRoot };
            if (!doc.SecurityStore.AddVerification(sig, vp))
                throw new Exception($"Could not add verification for {sig.Name}.");

            // Save the PDF to a file using incremental update so that the signature remains valid:
            doc.Save("B-LT.pdf", SaveMode.IncrementalUpdate);
        }

        // Adds time stamp to a signed PDF (e.g. as created by the do_B_LT method above),
        // which makes the document compliant with B-LTA level:
        void Do_B_LTA(string fn)
        {
            using FileStream fs = File.OpenRead(fn);
            var doc = new GcPdfDocument();
            doc.Load(fs);

            var ts = new TimeStampProperties()
            {
                TimeStamp = new TimeStamp(@"http://ts.ssl.com"),
            };
            // Save the PDF to a file adding a time stamp to it:
            doc.TimeStamp(ts, "B-LTA.pdf");
        }

        // Adds verification information for a PDF time-stamp (e.g. as created by the do_B_LTA method above)
        // which makes the signature LTV enabled:
        void Do_B_LTA_LTV(string fn)
        {
            using FileStream fs = File.OpenRead(fn);
            var doc = new GcPdfDocument();
            doc.Load(fs);

            var signField = doc.AcroForm.Fields.FirstOrDefault(f_ => f_ is SignatureField);
            if (signField == null)
                throw new Exception("Could not find a signature field.");

            // Get the signature and add verification information for it:
            var sig = (Signature)signField.Value;
            if (!doc.SecurityStore.AddVerification(sig))
                throw new Exception($"Could not add verification for {sig.Name}.");

            // Save the PDF to a file using incremental update so that the signature remains valid:
            doc.Save("B-LTA_LTV.pdf", SaveMode.IncrementalUpdate);
        }

        public static List<string[]> GetSampleParamsList()
        {
            // Strings are name, description, info, rest are arbitrary strings:
            return new List<string[]>()
            {
                new string[] { "@b-sign/PAdES B-B Level", "How to sign a PDF complying with PAdES B-B level", "",
                    "PAdES-B-B.pdf" },
                new string[] { "@b-sign/PAdES B-T Level", "How to sign a PDF complying with PAdES B-T level", "",
                    "PAdES-B-T.pdf" },
                new string[] { "@b-sign/PAdES B-LT Level", "How to add LTV information to a signature", "",
                    "PAdES-B-LT.pdf" },
                new string[] { "@b-sign/PAdES B-LTA Level", "How to time stamp a signed PDF", "",
                    "PAdES-B-LTA.pdf" },
                new string[] { "@b-sign/LTV Enabled Signature", "How to make a signature LTV enabled", "",
                    "PAdES-B-LTA-LTV.pdf" },
            };
        }
    }
}