Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: PDF digital signature #411

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFramework>net7.0</TargetFramework>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Authors>Stefan Steiger and Contributors</Authors>
Expand Down
2 changes: 1 addition & 1 deletion MigraDocCore.Rendering/MigraDocCore.Rendering.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFramework>net7.0</TargetFramework>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Authors>Stefan Steiger and Contributors</Authors>
Expand Down
2 changes: 1 addition & 1 deletion PdfSharpCore.Charting/PdfSharpCore.Charting.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFramework>net7.0</TargetFramework>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Authors>Stefan Steiger and Contributors</Authors>
Expand Down
2 changes: 1 addition & 1 deletion PdfSharpCore.Test/PdfSharpCore.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFramework>net7.0</TargetFramework>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
Expand Down
3 changes: 1 addition & 2 deletions PdfSharpCore/Pdf.AcroForms/PdfAcroField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,9 @@ public sealed class PdfAcroFieldCollection : PdfArray
{ }

PdfAcroFieldCollection(PdfDocument document)
: base(document)
: base(document)
{ }


/// <summary>
/// Gets the names of all fields in the collection.
/// </summary>
Expand Down
139 changes: 136 additions & 3 deletions PdfSharpCore/Pdf.AcroForms/PdfSignatureField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,151 @@
// DEALINGS IN THE SOFTWARE.
#endregion

using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf.Annotations;
using PdfSharpCore.Pdf.Signatures;
using System;

namespace PdfSharpCore.Pdf.AcroForms
{
/// <summary>
/// Represents the signature field.
/// </summary>
public sealed class PdfSignatureField : PdfAcroField
{
private bool visible;

public string Reason
{
get
{
return Elements.GetDictionary(Keys.V).Elements.GetString(Keys.Reason);
}
set
{
Elements.GetDictionary(Keys.V).Elements[Keys.Reason] = new PdfString(value);
}
}

public string Location
{
get
{
return Elements.GetDictionary(Keys.V).Elements.GetString(Keys.Location);
}
set
{
Elements.GetDictionary(Keys.V).Elements[Keys.Location] = new PdfString(value);
}
}

public PdfItem Contents
{
get
{
return Elements.GetDictionary(Keys.V).Elements[Keys.Contents];
}
set
{
Elements.GetDictionary(Keys.V).Elements.Add(Keys.Contents, value);
}
}


public PdfItem ByteRange
{
get
{
return Elements.GetDictionary(Keys.V).Elements[Keys.ByteRange];
}
set
{
Elements.GetDictionary(Keys.V).Elements.Add(Keys.ByteRange, value);
}
}


public PdfRectangle Rectangle
{
get
{
return (PdfRectangle)Elements[Keys.Rect];
}
set
{
Elements.Add(Keys.Rect, value);
this.visible = !(value.X1 + value.X2 + value.Y1 + value.Y2 == 0);

}
}


public ISignatureAppearanceHandler AppearanceHandler { get; internal set; }

/// <summary>
/// Initializes a new instance of PdfSignatureField.
/// </summary>
internal PdfSignatureField(PdfDocument document)
: base(document)
{ }
internal PdfSignatureField(PdfDocument document) : base(document)
{


Elements.Add(Keys.FT, new PdfName("/Sig"));
Elements.Add(Keys.T, new PdfString("Signature1"));
Elements.Add(Keys.Ff, new PdfInteger(132));
Elements.Add(Keys.DR, new PdfDictionary());
Elements.Add(Keys.Type, new PdfName("/Annot"));
Elements.Add(Keys.Subtype, new PdfName("/Widget"));
Elements.Add(Keys.P, document.Pages[0]);


PdfDictionary sign = new PdfDictionary(document);
sign.Elements.Add(Keys.Type, new PdfName("/Sig"));
sign.Elements.Add(Keys.Filter, new PdfName("/Adobe.PPKLite"));
sign.Elements.Add(Keys.SubFilter, new PdfName("/adbe.pkcs7.detached"));
sign.Elements.Add(Keys.M, new PdfDate(DateTime.Now));

document._irefTable.Add(sign);
document._irefTable.Add(this);

Elements.Add(Keys.V, sign);

}

internal PdfSignatureField(PdfDictionary dict)
: base(dict)
{ }


internal override void PrepareForSave()
{
if (!this.visible)
return;

if (this.AppearanceHandler == null)
throw new Exception("AppearanceHandler is null");



PdfRectangle rect = Elements.GetRectangle(PdfAnnotation.Keys.Rect);
XForm form = new XForm(this._document, rect.Size);
XGraphics gfx = XGraphics.FromForm(form);

this.AppearanceHandler.DrawAppearance(gfx, rect.ToXRect());

form.DrawingFinished();

// Get existing or create new appearance dictionary
PdfDictionary ap = Elements[PdfAnnotation.Keys.AP] as PdfDictionary;
if (ap == null)
{
ap = new PdfDictionary(this._document);
Elements[PdfAnnotation.Keys.AP] = ap;
}

// Set XRef to normal state
ap.Elements["/N"] = form.PdfForm.Reference;
}

/// <summary>
/// Predefined keys of this dictionary.
/// The description comes from PDF 1.4 Reference.
Expand Down Expand Up @@ -113,6 +240,12 @@ internal PdfSignatureField(PdfDictionary dict)
[KeyInfo(KeyType.TextString | KeyType.Optional)]
public const string Reason = "/Reason";

/// <summary>
/// (Optional)
/// </summary>
[KeyInfo(KeyType.TextString | KeyType.Optional)]
public const string ContactInfo = "/ContactInfo";

/// <summary>
/// Gets the KeysMeta for these keys.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion PdfSharpCore/Pdf.Advanced/PdfContents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ internal override void WriteObject(PdfWriter writer)
{
// Save two bytes in PDF stream...
if (Elements.Count == 1)
Elements[0].WriteObject(writer);
Elements[0].Write(writer);
else
base.WriteObject(writer);
}
Expand Down
2 changes: 1 addition & 1 deletion PdfSharpCore/Pdf.Advanced/PdfInternals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ public void WriteObject(Stream stream, PdfItem item)
// Never write an encrypted object
PdfWriter writer = new PdfWriter(stream, null);
writer.Options = PdfWriterOptions.OmitStream;
item.WriteObject(writer);
item.Write(writer);
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions PdfSharpCore/Pdf.IO/PdfWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,8 @@ public void Write(PdfString value)
WriteSeparator(CharCat.Delimiter);
PdfStringEncoding encoding = (PdfStringEncoding)(value.Flags & PdfStringFlags.EncodingMask);
string pdf = (value.Flags & PdfStringFlags.HexLiteral) == 0 ?
PdfEncoders.ToStringLiteral(value.EncryptionValue, encoding == PdfStringEncoding.Unicode, SecurityHandler) :
PdfEncoders.ToHexStringLiteral(value.EncryptionValue, encoding == PdfStringEncoding.Unicode, SecurityHandler);
PdfEncoders.ToStringLiteral(value.Value, PdfStringEncoding.Unicode, SecurityHandler) :
PdfEncoders.ToHexStringLiteral(value.Value, encoding, SecurityHandler, value.PaddingLeft);
WriteRaw(pdf);

_lastCat = CharCat.Delimiter;
Expand Down
11 changes: 9 additions & 2 deletions PdfSharpCore/Pdf.Internal/PdfEncoders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,9 @@ public static string ToStringLiteral(byte[] bytes, bool unicode, PdfStandardSecu
/// <summary>
/// Converts a raw string into a raw hexadecimal string literal, possibly encrypted.
/// </summary>
public static string ToHexStringLiteral(string text, PdfStringEncoding encoding, PdfStandardSecurityHandler securityHandler)
public static string ToHexStringLiteral(string text, PdfStringEncoding encoding, PdfStandardSecurityHandler securityHandler, int paddingLeft)
{
if (String.IsNullOrEmpty(text))
if (String.IsNullOrEmpty(text) && paddingLeft == 0)
return "<>";

byte[] bytes;
Expand All @@ -274,6 +274,13 @@ public static string ToHexStringLiteral(string text, PdfStringEncoding encoding,
throw new NotImplementedException(encoding.ToString());
}

if (bytes.Length < paddingLeft)
{
byte[] tmp = new byte[paddingLeft];
Array.Copy(bytes, tmp, bytes.Length);
bytes = tmp;
}

byte[] agTemp = FormatStringLiteral(bytes, encoding == PdfStringEncoding.Unicode, true, true, securityHandler);
return RawEncoding.GetString(agTemp, 0, agTemp.Length);
}
Expand Down
32 changes: 32 additions & 0 deletions PdfSharpCore/Pdf.Signatures/DefaultAppearanceHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using PdfSharpCore.Drawing;
using PdfSharpCore.Drawing.Layout;

namespace PdfSharpCore.Pdf.Signatures
{
internal class DefaultAppearanceHandler : ISignatureAppearanceHandler
{
public string Location { get; set; }
public string Reason { get; set; }
public string Signer { get; set; }


public void DrawAppearance(XGraphics gfx, XRect rect)
{
var backColor = XColor.Empty;
var defaultText = string.Format("Signed by: {0}\nLocation: {1}\nReason: {2}\nDate: {3}", Signer, Location, Reason, DateTime.Now);

XFont font = new XFont("Verdana", 7, XFontStyle.Regular);

XTextFormatter txtFormat = new XTextFormatter(gfx);

var currentPosition = new XPoint(0, 0);

txtFormat.DrawString(defaultText,
font,
new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)),
new XRect(currentPosition.X, currentPosition.Y, rect.Width - currentPosition.X, rect.Height),
XStringFormats.TopLeft);
}
}
}
47 changes: 47 additions & 0 deletions PdfSharpCore/Pdf.Signatures/DefaultSigner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace PdfSharpCore.Pdf.Signatures
{
public class DefaultSigner : ISigner
{
public X509Certificate2 Certificate { get; private set; }

public DefaultSigner(X509Certificate2 Certificate)
{
this.Certificate = Certificate;
}

public byte[] GetSignedCms(Stream stream)
{
var range = new byte[stream.Length];

stream.Position = 0;
stream.Read(range, 0, range.Length);



var contentInfo = new ContentInfo(range);

SignedCms signedCms = new SignedCms(contentInfo, true);
CmsSigner signer = new CmsSigner(Certificate);
signer.UnsignedAttributes.Add(new Pkcs9SigningTime());

signedCms.ComputeSignature(signer, true);
var bytes = signedCms.Encode();

return bytes;
}



public string GetName()
{
return Certificate.GetNameInfo(X509NameType.SimpleName, false);
}
}
}
9 changes: 9 additions & 0 deletions PdfSharpCore/Pdf.Signatures/ISignatureAppearanceHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using PdfSharpCore.Drawing;

namespace PdfSharpCore.Pdf.Signatures
{
public interface ISignatureAppearanceHandler
{
void DrawAppearance(XGraphics gfx, XRect rect);
}
}
15 changes: 15 additions & 0 deletions PdfSharpCore/Pdf.Signatures/ISigner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace PdfSharpCore.Pdf.Signatures
{
public interface ISigner
{
byte[] GetSignedCms(Stream stream);

string GetName();

}
}
Loading