Skip to content

Commit

Permalink
feat: support of digital signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
Patrick Ammann committed Jun 2, 2022
1 parent bad9cc5 commit 30e816a
Show file tree
Hide file tree
Showing 20 changed files with 684 additions and 25 deletions.
26 changes: 25 additions & 1 deletion PdfSharpCore/Pdf.AcroForms/PdfAcroField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,10 @@ public sealed class PdfAcroFieldCollection : PdfArray
: base(array)
{ }

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

/// <summary>
/// Gets the names of all fields in the collection.
/// </summary>
Expand Down Expand Up @@ -553,7 +557,27 @@ public class Keys : KeysBase
[KeyInfo(KeyType.Integer | KeyType.Optional)]
public const string Q = "/Q";

// ReSharper restore InconsistentNaming
/// <summary>
/// (Optional) The type of PDF object that this dictionary describes; if present,
/// must be Sig for a signature dictionary.
/// </summary>
[KeyInfo(KeyType.Name | KeyType.Optional)]
public const string Type = "/Type";

/// <summary>
///
/// </summary>
[KeyInfo(KeyType.Name | KeyType.Required)]
public const string Subtype = "/Subtype";

/// <summary>
///
/// </summary>
[KeyInfo(KeyType.Rectangle | KeyType.Required)]
public const string Rect = "/Rect";

[KeyInfo(KeyType.Rectangle | KeyType.Required)]
public const string P = "/P";
}
}
}
147 changes: 137 additions & 10 deletions PdfSharpCore/Pdf.AcroForms/PdfSignatureField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,36 +27,157 @@
// 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.
/// </summary>
public new class Keys : PdfAcroField.Keys
{
/// <summary>
/// (Optional) The type of PDF object that this dictionary describes; if present,
/// must be Sig for a signature dictionary.
/// </summary>
[KeyInfo(KeyType.Name | KeyType.Optional)]
public const string Type = "/Type";
{

/// <summary>
/// (Required; inheritable) The name of the signature handler to be used for
Expand Down Expand Up @@ -113,6 +234,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
2 changes: 1 addition & 1 deletion PdfSharpCore/Pdf.IO/PdfWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ public void Write(PdfString value)
PdfStringEncoding encoding = (PdfStringEncoding)(value.Flags & PdfStringFlags.EncodingMask);
string pdf = (value.Flags & PdfStringFlags.HexLiteral) == 0 ?
PdfEncoders.ToStringLiteral(value.Value, encoding, SecurityHandler) :
PdfEncoders.ToHexStringLiteral(value.Value, encoding, 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

0 comments on commit 30e816a

Please sign in to comment.