{"id":26182832,"url":"https://github.com/coatsy/signworddoc","last_synced_at":"2025-04-14T23:29:08.566Z","repository":{"id":102381254,"uuid":"112347713","full_name":"coatsy/SignWordDoc","owner":"coatsy","description":"Uses the OpenXML SDK and Crypto APIs to add a digital signature to a Word Document","archived":false,"fork":false,"pushed_at":"2023-02-02T03:23:37.000Z","size":560,"stargazers_count":19,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-28T11:37:52.457Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/coatsy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-11-28T14:44:57.000Z","updated_at":"2024-10-29T15:10:00.000Z","dependencies_parsed_at":null,"dependency_job_id":"33070ad3-2cb2-41b1-b1fc-e1c01723dbed","html_url":"https://github.com/coatsy/SignWordDoc","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coatsy%2FSignWordDoc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coatsy%2FSignWordDoc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coatsy%2FSignWordDoc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coatsy%2FSignWordDoc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coatsy","download_url":"https://codeload.github.com/coatsy/SignWordDoc/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248977548,"owners_count":21192612,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-11T22:32:34.699Z","updated_at":"2025-04-14T23:29:08.559Z","avatar_url":"https://github.com/coatsy.png","language":"C#","readme":"# Sign Word Doc\nSample code for generating a word document from a template and a data source, then digitally signing it.\n\n## Content\n[SignWordDoc](https://github.com/coatsy/SignWordDoc/tree/master/SignWordDoc) is the working code\n\n[XmlSign](https://github.com/coatsy/SignWordDoc/tree/master/XmlSign) is a copy of code Wouter van Vugt posted 10 years or so ago and now seems to be unavailable (thanks way-back-machine)\n\n## Instructions\n\n### Note\nTo run this sample, you'll need an X509 certificate. I've included one in the project that I generated (using the [batch file in this folder](https://github.com/coatsy/SignWordDoc/blob/master/CreateCoatsyDocSign.cmd)). You may want to generate or even obtain your own. There's a good explanation of [using the MakeCert tool](https://blog.jayway.com/2014/09/03/creating-self-signed-certificates-with-makecert-exe-for-development/#comments) from [Elizabeth Andrews](https://blog.jayway.com/author/elizabethandrews/) which I used to craft the cmd file.\n\n## Architecture\nThe idea of the sample is to take a data source (in this case, mocked) and generate a merged document from a template document, then sign it using a certificate which becomes invalid if the document is altered in any way. The Open Packaging Convention (of which the Word docx format is an instance) allows for signing all or parts of a document. In this case we're signing all of the document.\n\nHere's the general process the sample uses:\n\n![Architecture Diagram](/images/Architecture.png)\n\n* After some basic parameter checking, the template document is merged with the data for this instance to create the document.\n* The Contents of the document are collected and a hash calculated with using the X509 certificate. This hash (and some other info) is stored in the document - this is the digital signature.\n* The signed file is written out to disk.\n\n## More Details\n\n### Merging the data with the template document\nThere are a number of approaches I've used in the past to do programmatic merging of data with an existing template. This time I've gone with one based on Content Controls.\n\nThe template document has the basic structure of the output required and wherever data needs to be inserted, I've added a content control with a well known name.\n\nThe procedure for adding a Content Control is quite straight-forward:\n\n1. Ensure the [Developer Tab is enabled](https://support.office.com/en-us/article/Show-the-Developer-tab-E1192344-5E56-4D45-931B-E5FD9BEA2D45) in Word\n\n2. With the cursor positiond where you want the data to be inserted, click the Plain Text Content Control button on the Developer Tab:\n\n![Insert Content Control](/images/InsertContentControl1.png)\n\n3. This will insert a content control at the cursor. With the content control selected, click the Properties button:\n\n![Open Content Control Properties](/images/InsertContentControl2.png)\n\n4. This will pop up the properties dialog for the control. Give the control a unique Tag - we'll be using that string to find the control programmatically:\n\n![Content Control Properties Dialog](/images/InsertContentControl3.png)\n\nThe code to insert the data programmatically is also pretty easy. Using a Linq query, we can get a reference to the content control:\n```csharp\nvar control = doc.MainDocumentPart.Document.Descendants\u003cSdtRun\u003e().Where(r =\u003e r.SdtProperties.GetFirstChild\u003cTag\u003e().Val.Value == contentContolName).FirstOrDefault();\n```\nNext we find the parent of that control and remove the control, adding a `Run` of our own containing the `Text` we're adding:\n```csharp\nvar parent = control.Parent;\ncontrol.Remove();\n\n// now add the text as a child of the parent (replacing the content control)\nparent.AppendChild\u003cRun\u003e(new Run(new Text(newText)));\n```\nDoing this with whole paragraphs is a bit more involved, because there's not an easy way to add a bunch of elements in the right place in the document. The approach I took was to find the content control, and add the element before the control, then remove the control:\n```csharp\n/// \u003csummary\u003e\n/// Finds a content control and replaces it with the OpenXML Part passed in\n/// Can be used for Paragraphs, Tables, etc\n/// \u003c/summary\u003e\n/// \u003cparam name=\"doc\"\u003eDocument to Search\u003c/param\u003e\n/// \u003cparam name=\"contentControlName\"\u003eName of the content control to replace\u003c/param\u003e\n/// \u003cparam name=\"content\"\u003eArray of OpenXML elements to insert\u003c/param\u003e\nprivate static void ReplaceContentControlOpenXML(WordprocessingDocument doc, string contentControlName, OpenXmlElement[] content)\n{\n    var control = doc.MainDocumentPart.Document.Descendants\u003cSdtBlock\u003e().Where(b =\u003e b.SdtProperties.GetFirstChild\u003cTag\u003e().Val.Value == contentControlName).FirstOrDefault();\n    if (control != null)\n    {\n        foreach (var element in content)\n        {\n            control.InsertBeforeSelf(element);\n        }\n        control.Remove();\n    }\n}\n```\n\n### Signing the merged document\nTo sign the document, you need a certificate and you need to use the Open Packaging Convention's facility for storing a hash based on the certificate. Wouter van Vugt did a great post about this 10 years ago and it still works great. I've linked to all the details in the [XmlSign/readme.md](/XmlSign/readme.md).\n\nI've also included a certificate I generated and used for testing, as well as a batch file you can modify to generate your own. There's a good explanation of [using the MakeCert tool](https://blog.jayway.com/2014/09/03/creating-self-signed-certificates-with-makecert-exe-for-development/#comments) from [Elizabeth Andrews](https://blog.jayway.com/author/elizabethandrews/) which I used to craft the cmd file.\n\n### Untrusted Certificates are Untrusted\nOf course, if you generate your own certificate, Word has no way to validate it. When you open the signed document, you'll see it's signed, but that the cert isn't validated:\n![The certificate is untrusted and couldn't be validated](/images/UntrustedCert1.png)\n![The certificate is untrusted and couldn't be validated](/images/UntrustedCert2.png)\n![The certificate is untrusted and couldn't be validated](/images/UntrustedCert3.png)\n\nIf you Click the link to trust the user's identity, then all will be well:\n\n![The certificate is trusted and could be validated](/images/TrustedCert1.png)\n![The certificate is trusted and could be validated](/images/TrustedCert2.png)\n![The certificate is trusted and could be validated](/images/TrustedCert3.png)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoatsy%2Fsignworddoc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoatsy%2Fsignworddoc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoatsy%2Fsignworddoc/lists"}