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

Support of Digital Signatures (PKCS#7) + TSA timestamp #48

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
adaf57e
expose PdfString's start and end bytes positions in Stream
julienrffr Oct 18, 2023
a545b4c
add PKCS#7 detached signature feature
julienrffr Oct 20, 2023
3dd39c2
compatibility for wpf and gdi projects
julienrffr Oct 20, 2023
349dacc
incorporate BouncySigner in PdfSharp + internally cache signature length
julienrffr Oct 23, 2023
7c055f6
exclude whole /Contents entry from digest instead of just its value
julienrffr Oct 24, 2023
80cdf02
properly add signature field to acroform array
julienrffr Oct 24, 2023
15675dc
fix wrong position in Release mode
julienrffr Oct 30, 2023
052ad9e
do not sign using obsolete SHA1 algorithm
julienrffr Oct 31, 2023
e8f82c5
fix signature byte ranges computation
julienrffr Oct 31, 2023
496c553
enable display of custom appearance for signature fields
julienrffr Oct 31, 2023
11e34ae
get rid from obsolete X509Certificate2.PrivateKey
julienrffr Nov 2, 2023
b5e5dac
Merge branch 'empira:master' into signature-feature
julienrffr Nov 7, 2023
8888cd1
Added PageIndex option to the PdfSignatureOptions
matheus-kirchesch Jan 9, 2024
14371b9
Merge pull request #2 from matheus-kirchesch-btor/signature-feature
julienrffr Jan 9, 2024
1311969
signature handler: check nullity of arguments + comments
julienrffr Jan 9, 2024
55ddebb
DefaultSigner: add the ability to attach a signature timestamp from TSA
julienrffr Feb 23, 2024
8a90349
fix issue of varying TSA answer size by adding a 10 byte margin
julienrffr Feb 26, 2024
5f8cc24
Merge branch 'master' into signature-feature
julienrffr Jun 7, 2024
712ef3a
compatibility of ArgumentNullException.ThrowIfNull
julienrffr Jun 7, 2024
7d5c01b
DSA not supported in netstandard2.0
julienrffr Jun 7, 2024
68d42bb
use long instead of int for Stream Position
julienrffr Jun 7, 2024
eb1e4bb
fix build (making signatures timestamp feature only available on net6+)
julienrffr Jun 11, 2024
22352a1
update BouncyCastle.Cryptography (vulnerability warning)
julienrffr Jun 11, 2024
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
227 changes: 115 additions & 112 deletions src/Directory.Packages.props

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@
<Compile Include="..\PdfSharp\Pdf.Annotations\enums\PdfAnnotationFlags.cs" Link="Pdf.Annotations\enums\PdfAnnotationFlags.cs" />
<Compile Include="..\PdfSharp\Pdf.Annotations\enums\PdfRubberStampAnnotationIcon.cs" Link="Pdf.Annotations\enums\PdfRubberStampAnnotationIcon.cs" />
<Compile Include="..\PdfSharp\Pdf.Annotations\enums\PdfTextAnnotationIcon.cs" Link="Pdf.Annotations\enums\PdfTextAnnotationIcon.cs" />
<Compile Include="..\PdfSharp\Pdf.Annotations\IAnnotationAppearanceHandler.cs" Link="Pdf.Annotations\IAnnotationAppearanceHandler.cs" />
<Compile Include="..\PdfSharp\Pdf.Annotations\PdfAnnotation.cs" Link="Pdf.Annotations\PdfAnnotation.cs" />
<Compile Include="..\PdfSharp\Pdf.Annotations\PdfAnnotations.cs" Link="Pdf.Annotations\PdfAnnotations.cs" />
<Compile Include="..\PdfSharp\Pdf.Annotations\PdfGenericAnnotation.cs" Link="Pdf.Annotations\PdfGenericAnnotation.cs" />
Expand Down Expand Up @@ -319,6 +320,14 @@
<Compile Include="..\PdfSharp\Pdf.Security\PdfSecurityHandler.cs" Link="Pdf.Security\PdfSecurityHandler.cs" />
<Compile Include="..\PdfSharp\Pdf.Security\PdfSecuritySettings.cs" Link="Pdf.Security\PdfSecuritySettings.cs" />
<Compile Include="..\PdfSharp\Pdf.Security\PdfStandardSecurityHandler.cs" Link="Pdf.Security\PdfStandardSecurityHandler.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\BouncySigner.cs" Link="Pdf.Signatures\BouncySigner.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\DefaultSignatureAppearanceHandler.cs" Link="Pdf.Signatures\DefaultSignatureAppearanceHandler.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\DefaultSigner.cs" Link="Pdf.Signatures\DefaultSigner.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\ISigner.cs" Link="Pdf.Signatures\ISigner.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\PdfArrayWithPadding.cs" Link="Pdf.Signatures\PdfArrayWithPadding.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\PdfSignatureHandler.cs" Link="Pdf.Signatures\PdfSignatureHandler.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\PdfSignatureOptions.cs" Link="Pdf.Signatures\PdfSignatureOptions.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\RangedStream.cs" Link="Pdf.Signatures\RangedStream.cs" />
<Compile Include="..\PdfSharp\Pdf.Structure\PdfAttributesBase.cs" Link="Pdf.Structure\PdfAttributesBase.cs" />
<Compile Include="..\PdfSharp\Pdf.Structure\PdfLayoutAttributes.cs" Link="Pdf.Structure\PdfLayoutAttributes.cs" />
<Compile Include="..\PdfSharp\Pdf.Structure\PdfMarkedContentReference.cs" Link="Pdf.Structure\PdfMarkedContentReference.cs" />
Expand Down Expand Up @@ -424,4 +433,8 @@
<InternalsVisibleTo Include="PDFsharp.TestHelper-gdi, PublicKey=00240000048000009400000006020000002400005253413100040000010001008794e803e566eccc3c9181f52c4f7044e5442cc2ce3cbba9fc11bc4186ba2e446cd31deea20c1a8f499e978417fad2bc74143a4f8398f7cf5c5c0271b0f7fe907c537cff28b9d582da41289d1dae90168a3da2a5ed1115210a18fdae832479d3e639ca4003286ba8b98dc9144615c040ed838981ac816112df3b5a9e7cab4fbb" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Security.Cryptography.Pkcs" />
<PackageReference Include="BouncyCastle.Cryptography" />
</ItemGroup>
</Project>
14 changes: 14 additions & 0 deletions src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@
<Compile Include="..\PdfSharp\Pdf.Annotations\enums\PdfAnnotationFlags.cs" Link="Pdf.Annotations\enums\PdfAnnotationFlags.cs" />
<Compile Include="..\PdfSharp\Pdf.Annotations\enums\PdfRubberStampAnnotationIcon.cs" Link="Pdf.Annotations\enums\PdfRubberStampAnnotationIcon.cs" />
<Compile Include="..\PdfSharp\Pdf.Annotations\enums\PdfTextAnnotationIcon.cs" Link="Pdf.Annotations\enums\PdfTextAnnotationIcon.cs" />
<Compile Include="..\PdfSharp\Pdf.Annotations\IAnnotationAppearanceHandler.cs" Link="Pdf.Annotations\IAnnotationAppearanceHandler.cs" />
<Compile Include="..\PdfSharp\Pdf.Annotations\PdfAnnotation.cs" Link="Pdf.Annotations\PdfAnnotation.cs" />
<Compile Include="..\PdfSharp\Pdf.Annotations\PdfAnnotations.cs" Link="Pdf.Annotations\PdfAnnotations.cs" />
<Compile Include="..\PdfSharp\Pdf.Annotations\PdfGenericAnnotation.cs" Link="Pdf.Annotations\PdfGenericAnnotation.cs" />
Expand Down Expand Up @@ -318,6 +319,14 @@
<Compile Include="..\PdfSharp\Pdf.Security\PdfSecurityHandler.cs" Link="Pdf.Security\PdfSecurityHandler.cs" />
<Compile Include="..\PdfSharp\Pdf.Security\PdfSecuritySettings.cs" Link="Pdf.Security\PdfSecuritySettings.cs" />
<Compile Include="..\PdfSharp\Pdf.Security\PdfStandardSecurityHandler.cs" Link="Pdf.Security\PdfStandardSecurityHandler.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\BouncySigner.cs" Link="Pdf.Signatures\BouncySigner.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\DefaultSignatureAppearanceHandler.cs" Link="Pdf.Signatures\DefaultSignatureAppearanceHandler.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\DefaultSigner.cs" Link="Pdf.Signatures\DefaultSigner.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\ISigner.cs" Link="Pdf.Signatures\ISigner.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\PdfArrayWithPadding.cs" Link="Pdf.Signatures\PdfArrayWithPadding.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\PdfSignatureHandler.cs" Link="Pdf.Signatures\PdfSignatureHandler.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\PdfSignatureOptions.cs" Link="Pdf.Signatures\PdfSignatureOptions.cs" />
<Compile Include="..\PdfSharp\Pdf.Signatures\RangedStream.cs" Link="Pdf.Signatures\RangedStream.cs" />
<Compile Include="..\PdfSharp\Pdf.Structure\PdfAttributesBase.cs" Link="Pdf.Structure\PdfAttributesBase.cs" />
<Compile Include="..\PdfSharp\Pdf.Structure\PdfLayoutAttributes.cs" Link="Pdf.Structure\PdfLayoutAttributes.cs" />
<Compile Include="..\PdfSharp\Pdf.Structure\PdfMarkedContentReference.cs" Link="Pdf.Structure\PdfMarkedContentReference.cs" />
Expand Down Expand Up @@ -430,4 +439,9 @@
<PackageReference Include="System.Reflection.MetadataLoadContext" />
</ItemGroup>-->

<ItemGroup>
<PackageReference Include="System.Security.Cryptography.Pkcs" />
<PackageReference Include="BouncyCastle.Cryptography" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ public PdfAcroFieldCollection Fields
/// </summary>
public sealed class PdfAcroFieldCollection : PdfArray
{
PdfAcroFieldCollection(PdfArray array)
internal PdfAcroFieldCollection(PdfArray array)
: base(array)
{ }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// PDFsharp - A .NET library for processing PDF
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.

using PdfSharp.Pdf.IO;
using PdfSharp.Drawing;
using PdfSharp.Pdf.Annotations;

namespace PdfSharp.Pdf.AcroForms
{
Expand All @@ -21,6 +23,51 @@ internal PdfSignatureField(PdfDictionary dict)
: base(dict)
{ }

public IAnnotationAppearanceHandler CustomAppearanceHandler { get; internal set; }

/// <summary>
/// Creates the custom appearance form X object for the annotation that represents
/// this acro form text field.
/// </summary>
void RenderCustomAppearance()
{
PdfRectangle rect = Elements.GetRectangle(PdfAnnotation.Keys.Rect);

var visible = !(rect.X1 + rect.X2 + rect.Y1 + rect.Y2 == 0);

if (!visible)
return;

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

XForm form = new XForm(_document, rect.Size);
XGraphics gfx = XGraphics.FromForm(form);

CustomAppearanceHandler.DrawAppearance(gfx, rect.ToXRect());

form.DrawingFinished();

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

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

form.PdfRenderer.Close();
}

internal override void PrepareForSave()
{
base.PrepareForSave();
if (CustomAppearanceHandler != null)
RenderCustomAppearance();
}

/// <summary>
/// Writes a key/value pair of this signature field dictionary.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.

using PdfSharp.Drawing;

namespace PdfSharp.Pdf.Annotations
{
public interface IAnnotationAppearanceHandler
{
void DrawAppearance(XGraphics gfx, XRect rect);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.

using Org.BouncyCastle.Cms;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Collections;
using System.Security.Cryptography;
#if WPF
using System.IO;
#endif
using System.Security.Cryptography.X509Certificates;

namespace PdfSharp.Pdf.Signatures
{
public class BouncySigner : ISigner
{
private X509Certificate2 Certificate { get; set; }
private X509Certificate2Collection CertificateChain { get; }

public string GetName()
{
return Certificate.GetNameInfo(X509NameType.SimpleName, false);
}

public BouncySigner(Tuple<X509Certificate2, X509Certificate2Collection> certificateData)
{
this.Certificate = certificateData.Item1;
this.CertificateChain = certificateData.Item2;
}

public byte[] GetSignedCms(Stream rangedStream, int pdfVersion)
{
rangedStream.Position = 0;

CmsSignedDataGenerator signedDataGenerator = new CmsSignedDataGenerator();

var cert = DotNetUtilities.FromX509Certificate(Certificate);
var key = DotNetUtilities.GetKeyPair(GetAsymmetricAlgorithm(Certificate));
var allCerts = CertificateChain.OfType<X509Certificate2>().Select(item => DotNetUtilities.FromX509Certificate(item));

var store = CollectionUtilities.CreateStore(allCerts);

signedDataGenerator.AddSigner(key.Private, cert, GetProperDigestAlgorithm(pdfVersion));
signedDataGenerator.AddCertificates(store);

CmsProcessableInputStream msg = new CmsProcessableInputStream(rangedStream);

CmsSignedData signedData = signedDataGenerator.Generate(msg, false);

return signedData.GetEncoded();
}

/// <summary>
/// adbe.pkcs7.detached supported algorithms: SHA1 (PDF 1.3), SHA256 (PDF 1.6), SHA384/SHA512/RIPEMD160 (PDF 1.7)
/// </summary>
/// <param name="pdfVersion">PDF version as int</param>
/// <returns></returns>
private string GetProperDigestAlgorithm(int pdfVersion)
{
switch (pdfVersion)
{
case int when pdfVersion >= 17:
return CmsSignedDataGenerator.DigestSha512;
case int when pdfVersion == 16:
return CmsSignedDataGenerator.DigestSha256;
case int when pdfVersion >= 13:
default:
return CmsSignedDataGenerator.DigestSha256; // SHA1 is obsolete, use at least SHA256
}
}

private AsymmetricAlgorithm? GetAsymmetricAlgorithm(X509Certificate2 cert)
{
const String RSA = "1.2.840.113549.1.1.1";
const String DSA = "1.2.840.10040.4.1";
const String ECC = "1.2.840.10045.2.1";

return cert.PublicKey.Oid.Value switch
{
RSA => cert.GetRSAPrivateKey(),
#if NET6_0_OR_GREATER
DSA => cert.GetDSAPrivateKey(),
#endif
ECC => cert.GetECDsaPrivateKey(),
_ => throw new NotImplementedException(),
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.

using PdfSharp.Drawing;
using PdfSharp.Drawing.Layout;
using PdfSharp.Pdf.Annotations;

namespace PdfSharp.Pdf.Signatures
{
internal class DefaultSignatureAppearanceHandler : IAnnotationAppearanceHandler
{
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, XFontStyleEx.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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.

#if WPF
using System.IO;
#endif
#if NET6_0_OR_GREATER
using System.Net.Http;
using System.Net.Http.Headers;
#endif
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;

namespace PdfSharp.Pdf.Signatures
{
public class DefaultSigner : ISigner
{
private static readonly Oid SignatureTimeStampOin = new Oid("1.2.840.113549.1.9.16.2.14");
private static readonly string TimestampQueryContentType = "application/timestamp-query";
private static readonly string TimestampReplyContentType = "application/timestamp-reply";

private X509Certificate2 _certificate { get; init; }
private Uri? _timeStampAuthorityUri { get; init; }

public DefaultSigner(X509Certificate2 Certificate)
{
_certificate = Certificate;
}

#if NET6_0_OR_GREATER
/// <summary>
/// using a TimeStamp Authority to add timestamp to signature, only on net6+ for now due to available classes for Rfc3161
/// </summary>
/// <param name="Certificate"></param>
/// <param name="timeStampAuthorityUri"></param>
public DefaultSigner(X509Certificate2 Certificate, Uri? timeStampAuthorityUri = null)
{
_certificate = Certificate;
_timeStampAuthorityUri = timeStampAuthorityUri;
}
#endif

public byte[] GetSignedCms(Stream stream, int pdfVersion)
{
var range = new byte[stream.Length];
stream.Position = 0;
stream.Read(range, 0, range.Length);

// Sign the byte range
var contentInfo = new ContentInfo(range);
SignedCms signedCms = new SignedCms(contentInfo, true);
CmsSigner signer = new CmsSigner(_certificate)/* { IncludeOption = X509IncludeOption.WholeChain }*/;
signer.UnsignedAttributes.Add(new Pkcs9SigningTime());

signedCms.ComputeSignature(signer, true);

#if NET6_0_OR_GREATER
if (_timeStampAuthorityUri is not null)
Task.Run(() => AddTimestampFromTSAAsync(signedCms)).Wait();
#endif

var bytes = signedCms.Encode();

return bytes;
}

public string GetName()
{
return _certificate.GetNameInfo(X509NameType.SimpleName, false);
}

#if NET6_0_OR_GREATER
private async Task AddTimestampFromTSAAsync(SignedCms signedCms)
{
// Generate our nonce to identify the pair request-response
byte[] nonce = new byte[8];
#if NET6_0_OR_GREATER
nonce = RandomNumberGenerator.GetBytes(8);
#else
using var cryptoProvider = new RNGCryptoServiceProvider();
cryptoProvider.GetBytes(nonce = new Byte[8]);
#endif
// Get our signing information and create the RFC3161 request
SignerInfo newSignerInfo = signedCms.SignerInfos[0];
// Now we generate our request for us to send to our RFC3161 signing authority.
var request = Rfc3161TimestampRequest.CreateFromSignerInfo(
newSignerInfo,
HashAlgorithmName.SHA256,
requestSignerCertificates: true, // ask TSA to embed its signing certificate in the timestamp token
nonce: nonce);

var client = new HttpClient();
var content = new ReadOnlyMemoryContent(request.Encode());
content.Headers.ContentType = new MediaTypeHeaderValue(TimestampQueryContentType);
var httpResponse = await client.PostAsync(_timeStampAuthorityUri, content).ConfigureAwait(false);

// Process our response
if (!httpResponse.IsSuccessStatusCode)
{
throw new CryptographicException(
$"There was a error from the timestamp authority. It responded with {httpResponse.StatusCode} {(int)httpResponse.StatusCode}: {httpResponse.Content}");
}
if (httpResponse.Content.Headers.ContentType.MediaType != TimestampReplyContentType)
{
throw new CryptographicException("The reply from the time stamp server was in a invalid format.");
}
var data = await httpResponse.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
var timestampToken = request.ProcessResponse(data, out _);

// The RFC3161 sign certificate is separate to the contents that was signed, we need to add it to the unsigned attributes.
#if NET6_0_OR_GREATER
newSignerInfo.AddUnsignedAttribute(new AsnEncodedData(SignatureTimeStampOin, timestampToken.AsSignedCms().Encode()));
#else
newSignerInfo.UnsignedAttributes.Add(new AsnEncodedData(SignatureTimeStampOin, timestampToken.AsSignedCms().Encode()));
#endif
}
#endif
}
}
Loading