1+ // PDFsharp - A .NET library for processing PDF
2+ // See the LICENSE file in the solution root for more information.
3+
4+ #if WPF
5+ using System . IO ;
6+ #endif
7+ using System . Net . Http . Headers ;
8+ using System . Security . Cryptography ;
9+ using System . Security . Cryptography . Pkcs ;
10+ using System . Security . Cryptography . X509Certificates ;
11+
12+ namespace PdfSharp . Pdf . Signatures
13+ {
14+ public class DefaultSigner : ISigner
15+ {
16+ private static readonly Oid SignatureTimeStampOin = new Oid ( "1.2.840.113549.1.9.16.2.14" ) ;
17+ private static readonly string TimestampQueryContentType = "application/timestamp-query" ;
18+ private static readonly string TimestampReplyContentType = "application/timestamp-reply" ;
19+
20+ private readonly PdfSignatureOptions options ;
21+
22+ public DefaultSigner ( PdfSignatureOptions signatureOptions )
23+ {
24+ if ( signatureOptions ? . Certificate is null )
25+ throw new ArgumentException ( "Missing certificate in signature options" ) ;
26+
27+ options = signatureOptions ;
28+ }
29+
30+ public byte [ ] GetSignedCms ( Stream documentStream , PdfDocument document )
31+ {
32+ var range = new byte [ documentStream . Length ] ;
33+ documentStream . Position = 0 ;
34+ documentStream . Read ( range , 0 , range . Length ) ;
35+
36+ return GetSignedCms ( range , document ) ;
37+ }
38+
39+ public byte [ ] GetSignedCms ( byte [ ] range , PdfDocument document )
40+ {
41+ // Sign the byte range
42+ var contentInfo = new ContentInfo ( range ) ;
43+ var signedCms = new SignedCms ( contentInfo , true ) ;
44+ var signer = new CmsSigner ( options . Certificate ) /* { IncludeOption = X509IncludeOption.WholeChain }*/ ;
45+ signer . UnsignedAttributes . Add ( new Pkcs9SigningTime ( ) ) ;
46+
47+ signedCms . ComputeSignature ( signer , true ) ;
48+
49+ if ( options . TimestampAuthorityUri is not null )
50+ Task . Run ( ( ) => AddTimestampFromTSAAsync ( signedCms ) ) . Wait ( ) ;
51+
52+ var bytes = signedCms . Encode ( ) ;
53+
54+ return bytes ;
55+ }
56+
57+ public string ? GetName ( )
58+ {
59+ return options . Certificate ? . GetNameInfo ( X509NameType . SimpleName , false ) ;
60+ }
61+
62+ private async Task AddTimestampFromTSAAsync ( SignedCms signedCms )
63+ {
64+ // Generate our nonce to identify the pair request-response
65+ byte [ ] nonce = new byte [ 8 ] ;
66+ #if NET6_0_OR_GREATER
67+ nonce = RandomNumberGenerator . GetBytes ( 8 ) ;
68+ #else
69+ using var cryptoProvider = new RNGCryptoServiceProvider ( ) ;
70+ cryptoProvider . GetBytes ( nonce = new Byte [ 8 ] ) ;
71+ #endif
72+ #if NET6_0_OR_GREATER
73+ // Get our signing information and create the RFC3161 request
74+ SignerInfo newSignerInfo = signedCms . SignerInfos [ 0 ] ;
75+ // Now we generate our request for us to send to our RFC3161 signing authority.
76+ var request = Rfc3161TimestampRequest . CreateFromSignerInfo (
77+ newSignerInfo ,
78+ HashAlgorithmName . SHA256 ,
79+ requestSignerCertificates : true , // ask TSA to embed its signing certificate in the timestamp token
80+ nonce : nonce ) ;
81+
82+ var client = new HttpClient ( ) ;
83+ var content = new ReadOnlyMemoryContent ( request . Encode ( ) ) ;
84+ content . Headers . ContentType = new MediaTypeHeaderValue ( TimestampQueryContentType ) ;
85+ var httpResponse = await client . PostAsync ( options . TimestampAuthorityUri , content ) . ConfigureAwait ( false ) ;
86+
87+ // Process our response
88+ if ( ! httpResponse . IsSuccessStatusCode )
89+ {
90+ throw new CryptographicException (
91+ $ "There was a error from the timestamp authority. It responded with { httpResponse . StatusCode } { ( int ) httpResponse . StatusCode } : { httpResponse . Content } ") ;
92+ }
93+ if ( httpResponse . Content . Headers . ContentType ? . MediaType != TimestampReplyContentType )
94+ {
95+ throw new CryptographicException ( "The reply from the time stamp server was in a invalid format." ) ;
96+ }
97+ var data = await httpResponse . Content . ReadAsByteArrayAsync ( ) . ConfigureAwait ( false ) ;
98+ var timestampToken = request . ProcessResponse ( data , out _ ) ;
99+
100+ // The RFC3161 sign certificate is separate to the contents that was signed, we need to add it to the unsigned attributes.
101+ newSignerInfo . AddUnsignedAttribute ( new AsnEncodedData ( SignatureTimeStampOin , timestampToken . AsSignedCms ( ) . Encode ( ) ) ) ;
102+ #endif
103+ }
104+ }
105+ }
0 commit comments