Skip to content

Commit 73d5dbf

Browse files
author
EshwariBalraj
committed
991842- Digital signature sample is committed
1 parent 2ab41a6 commit 73d5dbf

File tree

11 files changed

+1507
-0
lines changed

11 files changed

+1507
-0
lines changed

How to/Secure Document Signing with PDF Digital Signature Support/client-side/app.js

Lines changed: 548 additions & 0 deletions
Large diffs are not rendered by default.

How to/Secure Document Signing with PDF Digital Signature Support/client-side/index.html

Lines changed: 524 additions & 0 deletions
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
using Microsoft.AspNetCore.Cors;
2+
using Microsoft.AspNetCore.Hosting;
3+
using Microsoft.AspNetCore.Mvc;
4+
using Syncfusion.Drawing;
5+
using Syncfusion.Pdf;
6+
using Syncfusion.Pdf.Graphics;
7+
using Syncfusion.Pdf.Parsing;
8+
using Syncfusion.Pdf.Security;
9+
using System;
10+
using System.Collections.Generic;
11+
using System.Globalization;
12+
using System.IO;
13+
using System.Text.Json;
14+
15+
16+
namespace DigitalSignServer.Controllers
17+
{
18+
[ApiController]
19+
[Route("api/[controller]")]
20+
public class PdfViewerController : ControllerBase
21+
{
22+
private readonly IWebHostEnvironment _env;
23+
private IConfiguration _configuration;
24+
25+
public PdfViewerController(IWebHostEnvironment env, IConfiguration configuration)
26+
{
27+
_env = env;
28+
_configuration = configuration;
29+
path = _configuration["DOCUMENT_PATH"];
30+
path = string.IsNullOrEmpty(path) ? Path.Combine(env.ContentRootPath, "Data") : Path.Combine(env.ContentRootPath, path);
31+
}
32+
string path;
33+
34+
private class Bounds
35+
{
36+
public int X { get; set; }
37+
public int Y { get; set; }
38+
public int Height { get; set; }
39+
public int Width { get; set; }
40+
}
41+
42+
// POST: /api/PdfViewer/AddVisibleSignature
43+
[HttpPost]
44+
[EnableCors("AllowAllOrigins")]
45+
[Route("AddVisibleSignature")]
46+
public IActionResult AddVisibleSignature([FromBody] Dictionary<string, object> jsonObject)
47+
{
48+
if (jsonObject != null && jsonObject.ContainsKey("pdfdata"))
49+
{
50+
string pdfdata = jsonObject["pdfdata"]?.ToString() ?? string.Empty;
51+
string[] split = pdfdata.Split(new string[] { "data:application/pdf;base64," }, StringSplitOptions.None);
52+
if (split.Length > 1)
53+
{
54+
string pdfdataString = split[1];
55+
if (!string.IsNullOrEmpty(pdfdataString))
56+
{
57+
// Load the PDF
58+
byte[] documentBytes = Convert.FromBase64String(pdfdataString);
59+
using PdfLoadedDocument loadedDocument = new PdfLoadedDocument(documentBytes);
60+
61+
// Existing field (first field) if user chose an existing field
62+
PdfLoadedSignatureField formField = null;
63+
if (loadedDocument.Form?.Fields?.Count > 0)
64+
{
65+
formField = loadedDocument.Form.Fields[0] as PdfLoadedSignatureField;
66+
}
67+
68+
// First page
69+
PdfPageBase loadedPage = loadedDocument.Pages[0];
70+
71+
// Certificate (update file name/password as needed)
72+
PdfCertificate pdfCertificate = new PdfCertificate(GetDocumentPath("localhost.pfx"), "Syncfusion@123");
73+
74+
// Optional signature image
75+
PdfImage image = null;
76+
if (jsonObject.ContainsKey("imagedata"))
77+
{
78+
string imageData = jsonObject["imagedata"]?.ToString() ?? string.Empty;
79+
string[] imgSplit = imageData.Split(new string[] {
80+
"data:image/png;base64,", "data:image/jpeg;base64,", "data:image/jpg;base64,"
81+
}, StringSplitOptions.None);
82+
83+
if (imgSplit.Length > 1 && !string.IsNullOrEmpty(imgSplit[1]))
84+
{
85+
byte[] imageBytes = Convert.FromBase64String(imgSplit[1]);
86+
MemoryStream imageStream = new MemoryStream(imageBytes);
87+
image = new PdfBitmap(imageStream);
88+
}
89+
}
90+
91+
// Signature text
92+
PdfStandardFont font = new PdfStandardFont(PdfFontFamily.Helvetica, 8);
93+
PdfSignature signature;
94+
string descriptionText = "";
95+
string signerName = "";
96+
string reason = "";
97+
string locationInfo = "";
98+
DateTime signingDate = DateTime.Now;
99+
100+
if (jsonObject.ContainsKey("signerName"))
101+
{
102+
signerName = jsonObject["signerName"]?.ToString() ?? "";
103+
if (!string.IsNullOrEmpty(signerName))
104+
descriptionText += "Digitally signed by " + signerName + "\n";
105+
}
106+
if (jsonObject.ContainsKey("reason"))
107+
{
108+
reason = jsonObject["reason"]?.ToString() ?? "";
109+
if (!string.IsNullOrEmpty(reason))
110+
descriptionText += "Reason: " + reason + "\n";
111+
}
112+
if (jsonObject.ContainsKey("location"))
113+
{
114+
locationInfo = jsonObject["location"]?.ToString() ?? "";
115+
if (!string.IsNullOrEmpty(locationInfo))
116+
descriptionText += "Location: " + locationInfo + "\n";
117+
}
118+
if (jsonObject.ContainsKey("date"))
119+
{
120+
string dateStr = jsonObject["date"]?.ToString() ?? "";
121+
if (!string.IsNullOrWhiteSpace(dateStr))
122+
{
123+
descriptionText += "Date: " + dateStr;
124+
125+
var formats = new[] { "MM-dd-yyyy", "MM-dd-yy" }; // support both if needed
126+
if (!DateTime.TryParseExact(dateStr, formats, CultureInfo.InvariantCulture,
127+
DateTimeStyles.None, out var givenDate))
128+
{
129+
return BadRequest($"Invalid date format: {dateStr}. Expected MM-dd-yyyy.");
130+
}
131+
132+
signingDate = new DateTime(
133+
givenDate.Year, givenDate.Month, givenDate.Day,
134+
signingDate.Hour, signingDate.Minute, signingDate.Second);
135+
}
136+
}
137+
138+
string displayMode = jsonObject.ContainsKey("displayMode")
139+
? jsonObject["displayMode"]?.ToString() ?? ""
140+
: "";
141+
142+
bool isSignatureField = jsonObject.ContainsKey("isSignatureField")
143+
&& bool.TryParse(jsonObject["isSignatureField"]?.ToString(), out bool v) && v;
144+
145+
if (isSignatureField)
146+
{
147+
// Sign existing field
148+
loadedDocument.FlattenAnnotations();
149+
signature = new PdfSignature(loadedDocument, loadedPage, pdfCertificate, "Signature", formField, signingDate);
150+
151+
if (!string.Equals(displayMode, "SIGNER DETAILS ONLY", StringComparison.OrdinalIgnoreCase) && image != null && formField != null)
152+
{
153+
float boundsWidth = formField.Bounds.Width * 0.57f;
154+
if (string.Equals(displayMode, "IMAGE ONLY", StringComparison.OrdinalIgnoreCase) || descriptionText.Length == 0)
155+
boundsWidth = formField.Bounds.Width;
156+
157+
float boundsHeight = formField.Bounds.Height;
158+
Bounds imgBounds = GetVisibleSignImageBounds(boundsHeight, boundsWidth, image.Height, image.Width);
159+
160+
signature.Appearance.Normal.Graphics.DrawImage(
161+
image,
162+
imgBounds.X, imgBounds.Y,
163+
imgBounds.Width, imgBounds.Height
164+
);
165+
}
166+
}
167+
else
168+
{
169+
// New signature with given bounds
170+
signature = new PdfSignature(loadedDocument, loadedPage, pdfCertificate, "Signature", signingDate);
171+
172+
Bounds signatureBounds = JsonSerializer.Deserialize<Bounds>(
173+
jsonObject["signatureBounds"]?.ToString() ?? "{}",
174+
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
175+
);
176+
177+
signature.Bounds = new Rectangle(signatureBounds.X, signatureBounds.Y, signatureBounds.Width, signatureBounds.Height);
178+
179+
if (!string.Equals(displayMode, "SIGNER DETAILS ONLY", StringComparison.OrdinalIgnoreCase) && image != null)
180+
{
181+
float boundsWidth = signatureBounds.Width * 0.57f;
182+
if (string.Equals(displayMode, "IMAGE ONLY", StringComparison.OrdinalIgnoreCase) || descriptionText.Length == 0)
183+
boundsWidth = signatureBounds.Width;
184+
185+
float boundsHeight = signatureBounds.Height;
186+
Bounds imgBounds = GetVisibleSignImageBounds(boundsHeight, boundsWidth, image.Height, image.Width);
187+
188+
signature.Appearance.Normal.Graphics.DrawImage(
189+
image,
190+
imgBounds.X, imgBounds.Y,
191+
imgBounds.Width, imgBounds.Height
192+
);
193+
}
194+
}
195+
196+
// Draw signer details text (if requested)
197+
if (!string.Equals(displayMode, "IMAGE ONLY", StringComparison.OrdinalIgnoreCase) && descriptionText.Length > 0)
198+
{
199+
PdfStringFormat format = new PdfStringFormat
200+
{
201+
Alignment = PdfTextAlignment.Left,
202+
LineAlignment = PdfVerticalAlignment.Middle
203+
};
204+
205+
if (string.Equals(displayMode, "WITH SIGNER DETAILS", StringComparison.OrdinalIgnoreCase))
206+
{
207+
signature.Appearance.Normal.Graphics.DrawString(
208+
descriptionText,
209+
font,
210+
PdfBrushes.Black,
211+
new RectangleF(signature.Bounds.Width * 0.6f, 0, signature.Bounds.Width * 0.4f, signature.Bounds.Height),
212+
format
213+
);
214+
}
215+
else
216+
{
217+
signature.Appearance.Normal.Graphics.DrawString(
218+
descriptionText,
219+
font,
220+
PdfBrushes.Black,
221+
new RectangleF(0, 0, signature.Bounds.Width, signature.Bounds.Height),
222+
format
223+
);
224+
}
225+
}
226+
227+
// Signature settings
228+
if (jsonObject.ContainsKey("signatureType") || jsonObject.ContainsKey("digestAlgorithm"))
229+
{
230+
PdfSignatureSettings settings = signature.Settings;
231+
232+
if (jsonObject.ContainsKey("signatureType"))
233+
{
234+
string sigType = jsonObject["signatureType"]?.ToString() ?? "";
235+
if (string.Equals(sigType, "CADES", StringComparison.OrdinalIgnoreCase))
236+
settings.CryptographicStandard = CryptographicStandard.CADES;
237+
else if (string.Equals(sigType, "CMS", StringComparison.OrdinalIgnoreCase))
238+
settings.CryptographicStandard = CryptographicStandard.CMS;
239+
}
240+
241+
if (jsonObject.ContainsKey("digestAlgorithm"))
242+
{
243+
string algo = jsonObject["digestAlgorithm"]?.ToString() ?? "";
244+
settings.DigestAlgorithm = (DigestAlgorithm)Enum.Parse(typeof(DigestAlgorithm), algo, ignoreCase: true);
245+
}
246+
}
247+
248+
signature.Certificated = true;
249+
250+
// Save
251+
using MemoryStream str = new MemoryStream();
252+
loadedDocument.Save(str);
253+
loadedDocument.Close(true);
254+
255+
byte[] docBytes = str.ToArray();
256+
string docBase64 = "data:application/pdf;base64," + Convert.ToBase64String(docBytes);
257+
258+
return Content(docBase64);
259+
}
260+
}
261+
}
262+
263+
return Content("data:application/pdf;base64,");
264+
}
265+
266+
private Bounds GetVisibleSignImageBounds(float boundsHeight, float boundsWidth, float imageHeight, float imageWidth)
267+
{
268+
// calculate aspect ratios
269+
float imageAspect = imageWidth / imageHeight;
270+
float boundsAspect = boundsWidth / boundsHeight;
271+
float drawWidth, drawHeight, offsetX, offsetY;
272+
if (imageAspect > boundsAspect)
273+
{
274+
// Image is wider relative to bounds
275+
drawWidth = boundsWidth;
276+
drawHeight = boundsWidth / imageAspect;
277+
offsetX = 0;
278+
offsetY = (boundsHeight - drawHeight) / 2;
279+
}
280+
else
281+
{
282+
// Image is taller relative to bounds
283+
drawHeight = boundsHeight;
284+
drawWidth = boundsHeight * imageAspect;
285+
offsetX = (boundsWidth - drawWidth) / 2;
286+
offsetY = 0;
287+
}
288+
return new Bounds() { X = (int)offsetX, Y = (int)offsetY, Width = (int)drawWidth, Height = (int)drawHeight };
289+
}
290+
291+
292+
private string GetDocumentPath(string document)
293+
{
294+
if (!System.IO.File.Exists(document))
295+
{
296+
string documentPath = Path.Combine(path, document);
297+
if (System.IO.File.Exists(documentPath))
298+
return documentPath;
299+
else
300+
return string.Empty;
301+
}
302+
else
303+
return document;
304+
}
305+
}
306+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
11+
<PackageReference Include="Syncfusion.Pdf.Net.Core" Version="31.2.5" />
12+
</ItemGroup>
13+
14+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<ActiveDebugProfile>IIS Express</ActiveDebugProfile>
5+
</PropertyGroup>
6+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
7+
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
8+
</PropertyGroup>
9+
</Project>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.14.36301.6 d17.14
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalSignServer", "DigitalSignServer.csproj", "{2C5CADA7-D8AA-48A3-A23F-E6A8B3756674}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{2C5CADA7-D8AA-48A3-A23F-E6A8B3756674}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{2C5CADA7-D8AA-48A3-A23F-E6A8B3756674}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{2C5CADA7-D8AA-48A3-A23F-E6A8B3756674}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{2C5CADA7-D8AA-48A3-A23F-E6A8B3756674}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {FC77C64E-CF6F-4714-8E86-7FB39B234386}
24+
EndGlobalSection
25+
EndGlobal
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Newtonsoft.Json.Serialization;
2+
using Syncfusion.Licensing;
3+
4+
var builder = WebApplication.CreateBuilder(args);
5+
6+
var MyAllowSpecificOrigins = "AllowAllOrigins";
7+
8+
builder.Services.AddControllers();
9+
builder.Services.AddCors(options =>
10+
{
11+
options.AddPolicy(name: MyAllowSpecificOrigins,
12+
builder => {
13+
builder.AllowAnyOrigin()
14+
.AllowAnyMethod()
15+
.AllowAnyHeader();
16+
});
17+
});
18+
19+
var app = builder.Build();
20+
21+
app.MapGet("/", () => "Hello World!");
22+
23+
app.UseHttpsRedirection();
24+
app.UseCors(MyAllowSpecificOrigins);
25+
app.MapControllers();
26+
app.Run();

0 commit comments

Comments
 (0)