Skip to content

Commit 30e816a

Browse files
author
Patrick Ammann
committed
feat: support of digital signatures
1 parent bad9cc5 commit 30e816a

20 files changed

+684
-25
lines changed

PdfSharpCore/Pdf.AcroForms/PdfAcroField.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,10 @@ public sealed class PdfAcroFieldCollection : PdfArray
300300
: base(array)
301301
{ }
302302

303+
PdfAcroFieldCollection(PdfDocument document)
304+
: base(document)
305+
{ }
306+
303307
/// <summary>
304308
/// Gets the names of all fields in the collection.
305309
/// </summary>
@@ -553,7 +557,27 @@ public class Keys : KeysBase
553557
[KeyInfo(KeyType.Integer | KeyType.Optional)]
554558
public const string Q = "/Q";
555559

556-
// ReSharper restore InconsistentNaming
560+
/// <summary>
561+
/// (Optional) The type of PDF object that this dictionary describes; if present,
562+
/// must be Sig for a signature dictionary.
563+
/// </summary>
564+
[KeyInfo(KeyType.Name | KeyType.Optional)]
565+
public const string Type = "/Type";
566+
567+
/// <summary>
568+
///
569+
/// </summary>
570+
[KeyInfo(KeyType.Name | KeyType.Required)]
571+
public const string Subtype = "/Subtype";
572+
573+
/// <summary>
574+
///
575+
/// </summary>
576+
[KeyInfo(KeyType.Rectangle | KeyType.Required)]
577+
public const string Rect = "/Rect";
578+
579+
[KeyInfo(KeyType.Rectangle | KeyType.Required)]
580+
public const string P = "/P";
557581
}
558582
}
559583
}

PdfSharpCore/Pdf.AcroForms/PdfSignatureField.cs

Lines changed: 137 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,36 +27,157 @@
2727
// DEALINGS IN THE SOFTWARE.
2828
#endregion
2929

30+
using PdfSharpCore.Drawing;
31+
using PdfSharpCore.Pdf.Annotations;
32+
using PdfSharpCore.Pdf.Signatures;
33+
using System;
34+
3035
namespace PdfSharpCore.Pdf.AcroForms
3136
{
3237
/// <summary>
3338
/// Represents the signature field.
3439
/// </summary>
3540
public sealed class PdfSignatureField : PdfAcroField
3641
{
42+
private bool visible;
43+
44+
public string Reason
45+
{
46+
get
47+
{
48+
return Elements.GetDictionary(Keys.V).Elements.GetString(Keys.Reason);
49+
}
50+
set
51+
{
52+
Elements.GetDictionary(Keys.V).Elements[Keys.Reason] = new PdfString(value);
53+
}
54+
}
55+
56+
public string Location
57+
{
58+
get
59+
{
60+
return Elements.GetDictionary(Keys.V).Elements.GetString(Keys.Location);
61+
}
62+
set
63+
{
64+
Elements.GetDictionary(Keys.V).Elements[Keys.Location] = new PdfString(value);
65+
}
66+
}
67+
68+
public PdfItem Contents
69+
{
70+
get
71+
{
72+
return Elements.GetDictionary(Keys.V).Elements[Keys.Contents];
73+
}
74+
set
75+
{
76+
Elements.GetDictionary(Keys.V).Elements.Add(Keys.Contents, value);
77+
}
78+
}
79+
80+
81+
public PdfItem ByteRange
82+
{
83+
get
84+
{
85+
return Elements.GetDictionary(Keys.V).Elements[Keys.ByteRange];
86+
}
87+
set
88+
{
89+
Elements.GetDictionary(Keys.V).Elements.Add(Keys.ByteRange, value);
90+
}
91+
}
92+
93+
94+
public PdfRectangle Rectangle
95+
{
96+
get
97+
{
98+
return (PdfRectangle)Elements[Keys.Rect];
99+
}
100+
set
101+
{
102+
Elements.Add(Keys.Rect, value);
103+
this.visible = !(value.X1 + value.X2 + value.Y1 + value.Y2 == 0);
104+
105+
}
106+
}
107+
108+
109+
public ISignatureAppearanceHandler AppearanceHandler { get; internal set; }
110+
37111
/// <summary>
38112
/// Initializes a new instance of PdfSignatureField.
39113
/// </summary>
40-
internal PdfSignatureField(PdfDocument document)
41-
: base(document)
42-
{ }
114+
internal PdfSignatureField(PdfDocument document) : base(document)
115+
{
116+
117+
118+
Elements.Add(Keys.FT, new PdfName("/Sig"));
119+
Elements.Add(Keys.T, new PdfString("Signature1"));
120+
Elements.Add(Keys.Ff, new PdfInteger(132));
121+
Elements.Add(Keys.DR, new PdfDictionary());
122+
Elements.Add(Keys.Type, new PdfName("/Annot"));
123+
Elements.Add(Keys.Subtype, new PdfName("/Widget"));
124+
Elements.Add(Keys.P, document.Pages[0]);
125+
126+
127+
PdfDictionary sign = new PdfDictionary(document);
128+
sign.Elements.Add(Keys.Type, new PdfName("/Sig"));
129+
sign.Elements.Add(Keys.Filter, new PdfName("/Adobe.PPKLite"));
130+
sign.Elements.Add(Keys.SubFilter, new PdfName("/adbe.pkcs7.detached"));
131+
sign.Elements.Add(Keys.M, new PdfDate(DateTime.Now));
132+
133+
document._irefTable.Add(sign);
134+
document._irefTable.Add(this);
135+
136+
Elements.Add(Keys.V, sign);
137+
138+
}
43139

44140
internal PdfSignatureField(PdfDictionary dict)
45141
: base(dict)
46142
{ }
47143

144+
145+
internal override void PrepareForSave()
146+
{
147+
if (!this.visible)
148+
return;
149+
150+
if (this.AppearanceHandler == null)
151+
throw new Exception("AppearanceHandler is null");
152+
153+
154+
155+
PdfRectangle rect = Elements.GetRectangle(PdfAnnotation.Keys.Rect);
156+
XForm form = new XForm(this._document, rect.Size);
157+
XGraphics gfx = XGraphics.FromForm(form);
158+
159+
this.AppearanceHandler.DrawAppearance(gfx, rect.ToXRect());
160+
161+
form.DrawingFinished();
162+
163+
// Get existing or create new appearance dictionary
164+
PdfDictionary ap = Elements[PdfAnnotation.Keys.AP] as PdfDictionary;
165+
if (ap == null)
166+
{
167+
ap = new PdfDictionary(this._document);
168+
Elements[PdfAnnotation.Keys.AP] = ap;
169+
}
170+
171+
// Set XRef to normal state
172+
ap.Elements["/N"] = form.PdfForm.Reference;
173+
}
174+
48175
/// <summary>
49176
/// Predefined keys of this dictionary.
50177
/// The description comes from PDF 1.4 Reference.
51178
/// </summary>
52179
public new class Keys : PdfAcroField.Keys
53-
{
54-
/// <summary>
55-
/// (Optional) The type of PDF object that this dictionary describes; if present,
56-
/// must be Sig for a signature dictionary.
57-
/// </summary>
58-
[KeyInfo(KeyType.Name | KeyType.Optional)]
59-
public const string Type = "/Type";
180+
{
60181

61182
/// <summary>
62183
/// (Required; inheritable) The name of the signature handler to be used for
@@ -113,6 +234,12 @@ internal PdfSignatureField(PdfDictionary dict)
113234
[KeyInfo(KeyType.TextString | KeyType.Optional)]
114235
public const string Reason = "/Reason";
115236

237+
/// <summary>
238+
/// (Optional)
239+
/// </summary>
240+
[KeyInfo(KeyType.TextString | KeyType.Optional)]
241+
public const string ContactInfo = "/ContactInfo";
242+
116243
/// <summary>
117244
/// Gets the KeysMeta for these keys.
118245
/// </summary>

PdfSharpCore/Pdf.Advanced/PdfContents.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ internal override void WriteObject(PdfWriter writer)
200200
{
201201
// Save two bytes in PDF stream...
202202
if (Elements.Count == 1)
203-
Elements[0].WriteObject(writer);
203+
Elements[0].Write(writer);
204204
else
205205
base.WriteObject(writer);
206206
}

PdfSharpCore/Pdf.Advanced/PdfInternals.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ public void WriteObject(Stream stream, PdfItem item)
268268
// Never write an encrypted object
269269
PdfWriter writer = new PdfWriter(stream, null);
270270
writer.Options = PdfWriterOptions.OmitStream;
271-
item.WriteObject(writer);
271+
item.Write(writer);
272272
}
273273

274274
/// <summary>

PdfSharpCore/Pdf.IO/PdfWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ public void Write(PdfString value)
199199
PdfStringEncoding encoding = (PdfStringEncoding)(value.Flags & PdfStringFlags.EncodingMask);
200200
string pdf = (value.Flags & PdfStringFlags.HexLiteral) == 0 ?
201201
PdfEncoders.ToStringLiteral(value.Value, encoding, SecurityHandler) :
202-
PdfEncoders.ToHexStringLiteral(value.Value, encoding, SecurityHandler);
202+
PdfEncoders.ToHexStringLiteral(value.Value, encoding, SecurityHandler, value.PaddingLeft);
203203
WriteRaw(pdf);
204204

205205
_lastCat = CharCat.Delimiter;

PdfSharpCore/Pdf.Internal/PdfEncoders.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,9 +245,9 @@ public static string ToStringLiteral(byte[] bytes, bool unicode, PdfStandardSecu
245245
/// <summary>
246246
/// Converts a raw string into a raw hexadecimal string literal, possibly encrypted.
247247
/// </summary>
248-
public static string ToHexStringLiteral(string text, PdfStringEncoding encoding, PdfStandardSecurityHandler securityHandler)
248+
public static string ToHexStringLiteral(string text, PdfStringEncoding encoding, PdfStandardSecurityHandler securityHandler, int paddingLeft)
249249
{
250-
if (String.IsNullOrEmpty(text))
250+
if (String.IsNullOrEmpty(text) && paddingLeft == 0)
251251
return "<>";
252252

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

277+
if (bytes.Length < paddingLeft)
278+
{
279+
byte[] tmp = new byte[paddingLeft];
280+
Array.Copy(bytes, tmp, bytes.Length);
281+
bytes = tmp;
282+
}
283+
277284
byte[] agTemp = FormatStringLiteral(bytes, encoding == PdfStringEncoding.Unicode, true, true, securityHandler);
278285
return RawEncoding.GetString(agTemp, 0, agTemp.Length);
279286
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using PdfSharpCore.Drawing;
3+
using PdfSharpCore.Drawing.Layout;
4+
5+
namespace PdfSharpCore.Pdf.Signatures
6+
{
7+
internal class DefaultAppearanceHandler : ISignatureAppearanceHandler
8+
{
9+
public string Location { get; set; }
10+
public string Reason { get; set; }
11+
public string Signer { get; set; }
12+
13+
14+
public void DrawAppearance(XGraphics gfx, XRect rect)
15+
{
16+
var backColor = XColor.Empty;
17+
var defaultText = string.Format("Signed by: {0}\nLocation: {1}\nReason: {2}\nDate: {3}", Signer, Location, Reason, DateTime.Now);
18+
19+
XFont font = new XFont("Verdana", 7, XFontStyle.Regular);
20+
21+
XTextFormatter txtFormat = new XTextFormatter(gfx);
22+
23+
var currentPosition = new XPoint(0, 0);
24+
25+
txtFormat.DrawString(defaultText,
26+
font,
27+
new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)),
28+
new XRect(currentPosition.X, currentPosition.Y, rect.Width - currentPosition.X, rect.Height),
29+
XStringFormats.TopLeft);
30+
}
31+
}
32+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Security.Cryptography.Pkcs;
5+
using System.Security.Cryptography.X509Certificates;
6+
using System.Text;
7+
8+
namespace PdfSharpCore.Pdf.Signatures
9+
{
10+
public class DefaultSigner : ISigner
11+
{
12+
public X509Certificate2 Certificate { get; private set; }
13+
14+
public DefaultSigner(X509Certificate2 Certificate)
15+
{
16+
this.Certificate = Certificate;
17+
}
18+
19+
public byte[] GetSignedCms(Stream stream)
20+
{
21+
var range = new byte[stream.Length];
22+
23+
stream.Position = 0;
24+
stream.Read(range, 0, range.Length);
25+
26+
27+
28+
var contentInfo = new ContentInfo(range);
29+
30+
SignedCms signedCms = new SignedCms(contentInfo, true);
31+
CmsSigner signer = new CmsSigner(Certificate);
32+
signer.UnsignedAttributes.Add(new Pkcs9SigningTime());
33+
34+
signedCms.ComputeSignature(signer, true);
35+
var bytes = signedCms.Encode();
36+
37+
return bytes;
38+
}
39+
40+
41+
42+
public string GetName()
43+
{
44+
return Certificate.GetNameInfo(X509NameType.SimpleName, false);
45+
}
46+
}
47+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using PdfSharpCore.Drawing;
2+
3+
namespace PdfSharpCore.Pdf.Signatures
4+
{
5+
public interface ISignatureAppearanceHandler
6+
{
7+
void DrawAppearance(XGraphics gfx, XRect rect);
8+
}
9+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Text;
5+
6+
namespace PdfSharpCore.Pdf.Signatures
7+
{
8+
public interface ISigner
9+
{
10+
byte[] GetSignedCms(Stream stream);
11+
12+
string GetName();
13+
14+
}
15+
}

0 commit comments

Comments
 (0)