SignAzureKeyVault.cs
// 
// このコードは、DioDocs for PDF のサンプルの一部として提供されています。
// © MESCIUS inc. All rights reserved.
// 
using System;
using System.IO;
using System.Drawing;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;

using Azure.Core;
using Azure.Identity;
using Azure.Security.KeyVault.Certificates;
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;

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


namespace DsPdfWeb.Demos
{
    // このサンプルでは、空の署名フィールドを含む既存の PDF ファイルに、
    // Azure Key Vault に格納された証明書を使用して署名する方法を
    // 示しています。
    //
    // サンプルには、GrapeCity.Documents.Pdf.IPkcs7SignatureGenerator インターフェースを実装した、
    // 実用的な AzureSignatureGenerator クラスが含まれており、Azure Key Vault に
    // 格納された証明書を使用して PDF に署名するために利用することができます。
    //  
    // なお、デモサイトで実行する場合、このサンプルはダミーの Azure 認証情報を
    // AzureSignatureGenerator クラスのコンストラクターに渡すため、PDF には署名しません。
    // 実際に PDF に署名するためには、サンプルをダウンロードし、サンプルコードに
    // 独自の認証情報を指定する必要があります。
    //
    public class SignAzureKeyVault
    {
        public int CreatePDF(Stream stream)
        {
            var doc = new GcPdfDocument();
            using var s = File.OpenRead(Path.Combine("Resources", "PDFs", "SignAzureKeyVault.pdf"));
            doc.Load(s);

            try
            {
                // ダミーの Azure 認証情報であるため、署名はしません。
                // 実際に PDF に署名するためには、有効な認証情報を指定してください。
                using var sg = new AzureSignatureGenerator(
                    "keyVaultName",
                    "tenantId",
                    "clientId",
                    "clientSecret",
                    "certificateName");

                var sp = new SignatureProperties()
                {
                    SignatureBuilder = new Pkcs7SignatureBuilder()
                    {
                        SignatureGenerator = sg,
                        CertificateChain = new X509Certificate2[] { sg.Certificate },
                    },
                    SignatureField = doc.AcroForm.Fields[0]
                };
                doc.Sign(sp, stream);
            }
            catch (Exception)
            {
                var page = doc.Pages[0];
                var r = doc.AcroForm.Fields[0].Widgets[0].Rect;
                Common.Util.AddNote(
                    "ダミーの Azure 認証情報が使用されたため、署名に失敗しました。\n" +
                    "PDF に署名するには、有効な Azure Key Vault の認証情報を指定してください。",
                    page,
                    new RectangleF(72, r.Bottom + 24, page.Size.Width - 72 * 2, 100));
                doc.Save(stream);
            }

            // 終了
            return doc.Pages.Count;
        }
    }

    /// <summary>
    /// <see cref="IPkcs7SignatureGenerator"/> を実装し、
    /// Azure Key Vault に格納された証明書を使用して
    /// デジタル署名を生成できるようにします。
    /// </summary>
    public class AzureSignatureGenerator : IPkcs7SignatureGenerator, IDisposable
    {
        private CertificateClient _certificateClient;
        private X509Certificate2 _certificate;
        private CryptographyClient _cryptographyClient;

        /// <summary>
        /// <see cref="AzureSignatureGenerator"/> クラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="keyVaultName">
        /// <see cref="CertificateClient"/> クラスのコンストラクタに渡す
        /// <b>https://{keyVaultName}.vault.azure.net/</b> 形式の URL を
        /// 作成するために使用される Key Vault ストレージの名前</param>
        /// <param name="tenantId">
        /// Azure Active Directory のテナント(ディレクトリ)のサービスプリンシパル ID
        /// (この値は <see cref="ClientSecretCredential"/> の作成に使用される)</param>
        /// <param name="clientId">
        /// クライアント(アプリケーション)のサービスプリンシパル ID
        /// (この値は <see cref="ClientSecretCredential"/> の作成に使用される)</param>
        /// <param name="clientSecret">
        /// クライアントの認証に使用される、アプリ登録用に生成されたクライアントシークレット
        /// (この値は <see cref="ClientSecretCredential"/> の作成に使用される)</param>
        /// <param name="certificateName">
        /// 署名に使用する証明書の名前</param>
        public AzureSignatureGenerator(
            string keyVaultName,
            string tenantId,
            string clientId,
            string clientSecret,
            string certificateName)
        {
            var keyVaultUri = new Uri($"https://{keyVaultName}.vault.azure.net/");
            TokenCredential credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
            _certificateClient = new CertificateClient(keyVaultUri, credential);
            var c = _certificateClient.GetCertificate(certificateName);
            _certificate = new X509Certificate2(c.Value.Cer);
            _cryptographyClient = new CryptographyClient(c.Value.KeyId, credential);
        }

        /// <summary>
        /// ハッシュアルゴリズムの ID を取得します。
        /// </summary>
        public OID HashAlgorithm => OID.HashAlgorithms.SHA256;

        /// <summary>
        /// 暗号化アルゴリズムの ID を取得します。
        /// </summary>
        public OID DigestEncryptionAlgorithm => OID.EncryptionAlgorithms.RSA;

        /// <summary>
        /// 証明書を取得します。
        /// </summary>
        public X509Certificate2 Certificate => _certificate;

        /// <summary>
        /// 署名データです。
        /// </summary>
        /// <param name="input">署名する入力データ</param>
        /// <returns>署名されたデータ</returns>
        public byte[] SignData(byte[] input)
        {
            var hashDigest = new Sha256Digest();
            byte[] hash = new byte[hashDigest.GetDigestSize()];
            hashDigest.Reset();
            hashDigest.BlockUpdate(input, 0, input.Length);
            hashDigest.DoFinal(hash, 0);
            byte[] result = _cryptographyClient.Sign(SignatureAlgorithm.RS256, hash).Signature;
            return result;
        }

        /// <summary>
        /// オブジェクトが使用しているリソースを解放します。
        /// </summary>
        public void Dispose()
        {
            _certificateClient = null;
            if (_certificate != null)
            {
                _certificate.Dispose();
                _certificate = null;
            }
            _cryptographyClient = null;
        }
    }
}