Word (RTF) Document Signature

This demo illustrates how to use the Office File API to add a digital signature to a DOCX document. Specify signing information on the Options pane and click Sign and Download to save the signed document.

Options
Reason
Role
Comments
Country
State
City
Address 1
Address 2
Postal Code
Hash algorithm
TSA server
@model AspNetCoreDemos.OfficeFileAPI.WordRTFDocumentSignatureModel
@using DevExtreme.AspNet.Mvc

@{ Html.BeginForm("WordRTFDocumentSignature", "DocumentProtection", FormMethod.Post); }

<div class="demo-view-container">
    @await Html.PartialAsync("WordRTFPreviewPartial", Model.PreviewModel)
</div>

@{
    List<DevExpress.Office.DigitalSignatures.CommitmentType> reasons = new List<DevExpress.Office.DigitalSignatures.CommitmentType>();
    reasons.Add(DevExpress.Office.DigitalSignatures.CommitmentType.ProofOfApproval);
    reasons.Add(DevExpress.Office.DigitalSignatures.CommitmentType.ProofOfCreation);
    reasons.Add(DevExpress.Office.DigitalSignatures.CommitmentType.ProofOfOrigin);
}

<div class="options no-right-padding">
    @(Html.DevExtreme().ScrollView()
                                       .ID("scrollview")
                                       .ScrollByContent(true)
                                       .ScrollByThumb(true)
                                       .ShowScrollbar(ShowScrollbarMode.OnHover)
                                       .Direction(ScrollDirection.Both)
                                       .Height("424px")
                                       .Content(@<div id="scrollview-content" class="scrollable-content-right-padding scrollable-content-width">
        <div class="caption">Options</div>
        <div class="option-buttons">
            @(Html.DevExtreme().Button()
                .Text("Sign and Download")
                .Type(ButtonType.Default)
                .StylingMode(ButtonStylingMode.Contained)
                .UseSubmitBehavior(true)
                .OnClick("SignClick")
            )
        </div>
        <div class="option">
            <div class="label">Reason</div>
            @(Html.DevExtreme().SelectBoxFor(m => m.ReasonId)
            .DataSource(reasons.Select(i => new { Value = i.Id, Text = i.Description }))
            .ValueExpr("Value")
            .DisplayExpr("Text"))
        </div>
        <div class="option">
            <div class="label">Role</div>
            @(Html.DevExtreme().TextBoxFor(m => m.Role))
        </div>
        <div class="option">
            <div class="label">Comments</div>
            @(Html.DevExtreme().TextBoxFor(m => m.Comments))
        </div>
        <div class="option">
            <div class="label">Country</div>
            @(Html.DevExtreme().TextBoxFor(m => m.Country))
        </div>
        <div class="option">
            <div class="label">State</div>
            @(Html.DevExtreme().TextBoxFor(m => m.State))
        </div>
        <div class="option">
            <div class="label">City</div>
            @(Html.DevExtreme().TextBoxFor(m => m.City))
        </div>
        <div class="option">
            <div class="label">Address 1</div>
            @(Html.DevExtreme().TextBoxFor(m => m.Address1))
        </div>
        <div class="option">
            <div class="label">Address 2</div>
            @(Html.DevExtreme().TextBoxFor(m => m.Address2))
        </div>
        <div class="option">
            <div class="label">Postal Code</div>
            @(Html.DevExtreme().TextBoxFor(m => m.PostalCode))
        </div>
        <div class="option">
            <div class="label">Hash algorithm</div>
            @(Html.DevExtreme().SelectBoxFor(m => m.HashAlgorithm)
            .DataSource(Html.GetEnumSelectList<DevExpress.Office.DigitalSignatures.HashAlgorithmType>()
            .Select(i => new { Value = int.Parse(i.Value), Text = i.Text }))
            .ValueExpr("Value")
            .DisplayExpr("Text"))
        </div>
        <div class="option">
            <div class="label">TSA server</div>
            @(Html.DevExtreme().TextBoxFor(m => m.TSAServer)
            .Mode(TextBoxMode.Url)
            .ID("tsaServerTextBox")
            .ValidationStatus(ViewBag.ErrorMessage != null ? ValidationStatus.Invalid : ValidationStatus.Valid))
        </div>
        <div id="error" class="error-message scrollable-content-width">
            <span>@ViewBag.ErrorMessage</span>
        </div>
    </div>)
    )
</div>

    <script type="text/javascript">
        function SignClick() {
            $("#tsaServerTextBox").dxTextBox("instance").option("validationStatus", 'valid');
            document.getElementById("error").hidden = true;
        }
    </script>
    @{ Html.EndForm(); }
@model AspNetCoreDemos.OfficeFileAPI.WordRTFPreviewModel

<iframe id="previewFrame" src="@Url.Action(Model.PreviewDocumentAction, Model.ControllerName)" height="@Model.IFrameSize" class="demo-preview-border" style="width:100%;box-sizing:border-box"></iframe>

<script type="text/javascript">
    WordRTFPreview = {
        basePath: '@Url.Action(Model.PreviewDocumentAction, Model.ControllerName)',
        Update: function (param) {
            var iframeElementName = "previewFrame";
            var iframeElement = document.getElementById(iframeElementName);
            if (!iframeElement)
                return;
            var additionalParams = "&" + new Date().valueOf();
            if (param)
                additionalParams = param;
            iframeElement.src = this.basePath + "?" + additionalParams;
        }
    };
</script>
using Microsoft.AspNetCore.Hosting;

namespace AspNetCoreDemos.OfficeFileAPI {
    public partial class DocumentProtectionController : OfficeDemoController {
        public DocumentProtectionController(IWebHostEnvironment hostingEnvironment)
            : base(hostingEnvironment) {
        }
    }
}
using System.IO;
using DevExpress.XtraRichEdit;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreDemos.OfficeFileAPI {
    public partial class DocumentProtectionController {
        public IActionResult WordRTFDocumentSignature() {
            WordRTFDocumentSignatureModel model = new WordRTFDocumentSignatureModel();
            return View(model);
        }   
        [HttpPost]
        public IActionResult WordRTFDocumentSignature(WordRTFDocumentSignatureModel model) {
            try {
                return CreateFileStreamResult(model.Sign(HostingEnvironment), "application/msword", "docx", "SignedDocument");
            } catch {
                ViewBag.ErrorMessage = "ERROR: TSA server URI is invalid or server doesn't support SHA-256 hashing algorithm";
                return View("WordRTFDocumentSignature", model);
            }
        }

        public IActionResult WordRTFDocumentSignaturePreview(WordRTFDocumentSignatureModel model) {
            Stream stream = CreateDocumentStream(DocumentFormat.Html);
            return CreatePreviewResult(stream);
        }

        Stream CreateDocumentStream(DocumentFormat documentFormat) {
            if(documentFormat == DocumentFormat.Undefined)
                return null;
            RichEditDocumentServer documentServer = CreateDocumentServer();
            string documentsFolderPath = HostingEnvironment.ContentRootPath + "/Documents/";
            documentServer.LoadDocument(documentsFolderPath + "DocumentForProtection.docx");
            MemoryStream result = new MemoryStream();
            documentServer.SaveDocument(result, documentFormat);
            result.Seek(0, SeekOrigin.Begin);
            return result;
        }
    }
}
using DevExpress.Office.DigitalSignatures;
using DevExpress.Office.Tsp;
using Microsoft.AspNetCore.Hosting;
using System;
using System.IO;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;

namespace AspNetCoreDemos.OfficeFileAPI {
    public class WordRTFDocumentSignatureModel : WordRTFModelBase {
        public string ReasonId { get; set; }
        public string Role { get; set; }
        public string Comments { get; set; }

        public string Country { get; set; }
        public string State { get; set; }
        public string City { get; set; }
        public string Address1 { get; set; }
        public string Address2 { get; set; }
        public string PostalCode { get; set; }

        public HashAlgorithmType HashAlgorithm { get; set; }
        public Uri TSAServer { get; set; }
        public string ErrorText { get; set; }

        public WordRTFDocumentSignatureModel() {
            PreviewModel.PreviewDocumentAction = "WordRTFDocumentSignaturePreview";

            ReasonId = CommitmentType.ProofOfApproval.Id;
            Role = "Sales Representative";
            Comments = "Demo Digital Signature";

            Country = "USA";
            State = "WA";
            City = "Seattle";
            Address1 = "507 - 20th Ave. E.";
            Address2 = "Apt. 2A";
            PostalCode = "98122";

            HashAlgorithm = HashAlgorithmType.SHA256;
            TSAServer = new Uri("https://freetsa.org/tsr");
        }
        public Stream Sign(IWebHostEnvironment hostingEnvironment) {
            string documentsFolderPath = hostingEnvironment.ContentRootPath + "/Documents/";
            using (Stream inputStream = new FileStream(documentsFolderPath + "DocumentForProtection.docx", FileMode.Open, FileAccess.Read, FileShare.Read)) {
                Stream stream = new MemoryStream();
                DocumentSigner documentSigner = new DocumentSigner();
                documentSigner.Sign(inputStream, stream, CreateSignatureOptions(hostingEnvironment), CreateSignatureInfo());
                return stream;
            }
        }
        SignatureOptions CreateSignatureOptions(IWebHostEnvironment hostingEnvironment) {
            string documentsFolderPath = hostingEnvironment.ContentRootPath + "/Documents/";
            X509Certificate2 certificate = new X509Certificate2(documentsFolderPath + "Pdf/SignDemo.pfx", "dxdemo");
            SignatureOptions options = new SignatureOptions();
            options.Certificate = certificate;

            if(TSAServer != null)
                options.TsaClient = new TsaClient(TSAServer, HashAlgorithmType.SHA256);

            X509ChainPolicy policy = new X509ChainPolicy();
            policy.RevocationMode = X509RevocationMode.NoCheck;
            policy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
            policy.VerificationFlags |= X509VerificationFlags.AllowUnknownCertificateAuthority | X509VerificationFlags.IgnoreCertificateAuthorityRevocationUnknown;
            options.CertificatePolicy = policy;
            options.TimestampCertificatePolicy = policy;
            options.SignatureFlags &= ~SignatureFlags.ValidateCertificate;
            options.CertificateKeyUsageFlags = X509KeyUsageFlags.None;
            options.DigestMethod = this.HashAlgorithm;
            return options;
        }
        SignatureInfo CreateSignatureInfo() {
            SignatureInfo signatureInfo = new SignatureInfo();
            signatureInfo.CommitmentType = GetCommitmentType(ReasonId);
            signatureInfo.Time = DateTime.UtcNow;
            signatureInfo.ClaimedRoles.Clear();
            signatureInfo.ClaimedRoles.Add(this.Role);
            signatureInfo.Country = this.Country;
            signatureInfo.City = this.City;
            signatureInfo.StateOrProvince = this.State;
            signatureInfo.Address1 = this.Address1;
            signatureInfo.Address2 = this.Address2;
            signatureInfo.PostalCode = this.PostalCode;
            signatureInfo.Comments = this.Comments;
            return signatureInfo;
        }
        CommitmentType GetCommitmentType(string id) {
            if (String.IsNullOrEmpty(id))
                return CommitmentType.ProofOfApproval;
            PropertyInfo[] properties = typeof(CommitmentType).GetProperties(BindingFlags.Static | BindingFlags.Public);
            if (properties == null || properties.Length <= 0)
                return CommitmentType.ProofOfApproval;

            foreach (PropertyInfo pi in properties) {
                CommitmentType commitmentType = pi.GetValue(null) as CommitmentType;
                if (commitmentType != null && commitmentType.Id == id)
                    return commitmentType;
            }

            return CommitmentType.ProofOfApproval;
        }
    }
}
namespace AspNetCoreDemos.OfficeFileAPI {
    public class WordRTFModelBase {
        public WordRTFModelBase() {
            PreviewModel = new WordRTFPreviewModel();
            PreviewModel.OwnerPropertyName = "PreviewModel";
            FileFormat = RichEditFileFormat.Rtf;
        }

        public RichEditFileFormat FileFormat { get; set; }
        public WordRTFPreviewModel PreviewModel { get; internal set; }
    }

    public class WordRTFPreviewModel {
        public WordRTFPreviewModel() {
        }

        public string OwnerPropertyName { get; set; }
        public string PreviewDocumentAction { get; set; }
        public string ControllerName { get; set; }
        public int IFrameSize { get; set; } = 452;
    }
}