PadesLevels.cs
// 
// このコードは、DioDocs for PDF のサンプルの一部として提供されています。
// © 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
{
    // このサンプルでは、空の署名フィールドを含む既存の PDF ファイルに、
    // PAdES(PDF 長期署名、PDF Advanced Electronic Signatures)標準の
    // 様々な署名レベルに準拠した署名を行う方法を紹介しています。
    // なお、オンラインで実行した場合、このサンプルは空の署名フィールドを含む
    // 未署名の PDF を生成するだけになります。実際に PDF に署名するには、
    // 有効な .pfx ファイル(「JohnDoe.pfx」を参照を用意する必要があります。
    // サンプルには、様々な PAdES レベルにて PDF に署名するために使用できる
    // 5つの方法が含まれており、最後の3つの方法を連鎖的に使用することで、
    // 署名の強度を段階的に上げることができます。
    // 署名のコンプライアンスを確認するには、Acrobat Reader で署名されたPDFを開き、
    // 署名のプロパティを調べてください。
    //
    // 重要! このサンプルに含まれるコードは、DsPdf に有効な
    // ライセンスキーが適用されていないと正しく動作しません。
    public class PadesLevels
    {
        // 認証局が発行した証明書を設定します。検証に使用します。
        static X509Certificate2 s_caCertRoot = new X509Certificate2(Path.Combine("Resources", "Misc", "CACertRoot.crt"));
        // 注:実際の証明書に置き換えてください。
        static X509Certificate2 s_cert = new X509Certificate2(Path.Combine("Resources", "Misc", "JohnDoe.pfx"), "secret");

        static PadesLevels()
        {
            // 注:署名が有効なまま PDF を保存するには、ライセンスキーが適用されている必要があります。
            // 適用されていない場合、PDF を保存する際に追加されるウォーターマークにより、
            // 増分更新が無効になります。
            // GcPdfDocument.SetLicenseKey("ライセンスキー");
        }

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

        // このサンプルには、実際に署名やタイムスタンプを追加するコードが含まれていますが、
        // そのコードはオンラインデモでは実行されません。そのため、オンラインサンプルでは、
        // 選択された PAdES レベルに関する情報を含む PDF を生成するだけですが、
        // 有効な証明書を使用すれば、実際に署名やタイムスタンプを追加できるようになります。
        // 対応するコードをコピーして、アプリケーションで使用することもできます。
        public void CreatePDF(Stream stream, string[] sampleParams)
        {
            // 注:署名/タイムスタンプを追加するコードを実際に実行するには、true に変更します。
            bool doSigning = false;

            // 空の署名フィールドを含む未署名の PDF を指定します。
            var fn = Path.Combine("Resources", "PDFs", sampleParams[3]);

            if (doSigning)
            {
                //このコードを実行すると、PAdES レベルが上がっていく5つのPDFが作成されます。

                // PAdES B-B
                Do_B_B(fn);
                // PAdES B-T
                Do_B_T(fn);
                // なお、次の3つのステップでは、前のステップで作成されたPDFを使用して、
                // 検証情報を段階的に追加していきます。
                // PAdES B-LT
                Do_B_LT("B-T.pdf");
                // PAdES B-LTA
                Do_B_LTA("B-LT.pdf");
                // LTV(長期検証、Long-Term Validation) が可能
                Do_B_LTA_LTV("B-LTA.pdf");
            }

            // デモ用の結果を出力するために、
            // 出力ストリームに元となる PDF をコピーします。
            using var fs = File.OpenRead(fn);
            fs.CopyTo(stream);
        }

        // PAdES B-B レベルの署名にて PDF に署名します。
        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("署名フィールドが見つかりませんでした。");

            var sp = new SignatureProperties()
            {
                SignatureField = signField,
                SignatureBuilder = new Pkcs7SignatureBuilder(s_cert)
                {
                    Format = Pkcs7SignatureBuilder.SignatureFormat.ETSI_CAdES_detached,
                },
            };
            // 署名して、PDF をファイルに保存します。
            doc.Sign(sp, "B-B.pdf");
        }

        // PAdES B-T レベルの署名にて PDF に署名します。
        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("署名フィールドが見つかりませんでした。");

            var sp = new SignatureProperties()
            {
                SignatureField = signField,
                SignatureBuilder = new Pkcs7SignatureBuilder(s_cert)
                {
                    Format = Pkcs7SignatureBuilder.SignatureFormat.ETSI_CAdES_detached,
                },
                TimeStamp = new TimeStamp(@"http://ts.ssl.com"),
            };
            // 署名して、PDF をファイルに保存します。
            doc.Sign(sp, "B-T.pdf");
        }

        // PAdES B-T レベルの署名(例:上記の Do_B_T メソッドで作成されたもの)に LTV 情報を追加し、
        // 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("署名フィールドが見つかりませんでした。");

            // 署名を取得し、その署名に対する検証情報を追加します。
            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($"{sig.Name} に対する検証を追加できませんでした。");

            // 署名が有効なままになるように、増分更新で PDF をファイルに保存します。
            doc.Save("B-LT.pdf", SaveMode.IncrementalUpdate);
        }

        // 署名された PDF(例:上記の Do_B_LT メソッドで作成されたもの)にタイムスタンプを追加し、
        // PAdES B-LTA レベルに準拠したドキュメントにします。
        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"),
            };
            // PDF にタイムスタンプを追加してファイルに保存します。
            doc.TimeStamp(ts, "B-LTA.pdf");
        }

        // PDF のタイムスタンプ(例:上記の Do_B_LTA メソッドで作成されたもの)に対して
        // 検証情報を追加し、LTV が可能な署名にします。
        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("署名フィールドが見つかりませんでした。");

            // 署名を取得し、その署名に対する検証情報を追加します。
            var sig = (Signature)signField.Value;
            if (!doc.SecurityStore.AddVerification(sig))
                throw new Exception($"{sig.Name} に対する検証を追加できませんでした。");

            // 署名が有効なままになるように、増分更新で PDF をファイルに保存します。
            doc.Save("B-LTA_LTV.pdf", SaveMode.IncrementalUpdate);
        }

        public static List<string[]> GetSampleParamsList()
        {
            // このリストでは、サンプルの名前、説明、情報、読み込む PDF ファイルを保持しています。
            return new List<string[]>()
            {
                new string[] { "@b-sign/PAdES B-B レベル", "PDF へ PAdES の B-B レベルに準拠した署名を行う", "",
                    "PAdES-B-B.pdf" },
                new string[] { "@b-sign/PAdES B-T レベル", "PDF へ PAdES の B-T レベルに準拠した署名を行う", "",
                    "PAdES-B-T.pdf" },
                new string[] { "@b-sign/PAdES B-LT レベル", "署名に長期検証情報を追加する", "",
                    "PAdES-B-LT.pdf" },
                new string[] { "@b-sign/PAdES B-LTA レベル", "署名された PDF にタイムスタンプを追加する", "",
                    "PAdES-B-LTA.pdf" },
                new string[] { "@b-sign/長期検証可能な署名", "長期検証が可能な署名を作成する", "",
                    "PAdES-B-LTA-LTV.pdf" },
            };
        }
    }
}