Certificates https://itextpdf.com/ en Attacks on PDF certification, and what you can do about them https://itextpdf.com/blog/itext-news-technical-notes/attacks-pdf-certification-and-what-you-can-do-about-them <span property="schema:name" class="field field--name-title field--type-string field--label-hidden">Attacks on PDF certification, and what you can do about them</span> <span rel="schema:author" class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/users/juliekluyskens" typeof="schema:Person" property="schema:name" datatype="">julie.kluyskens</span></span> <span property="schema:dateCreated" content="2021-06-23T12:56:35+00:00" class="field field--name-created field--type-created field--label-hidden"><time datetime="2021-06-23T14:56:35+02:00" title="Wednesday, June 23, 2021 - 14:56" class="datetime">Wed, 06/23/2021 - 14:56</time> </span> <div property="schema:text" class="wysiwyg-content clearfix text-formatted field field--name-body field--type-text-with-summary field__items"> <p><img alt="PDF under attack" data-entity-type="file" data-entity-uuid="4d59b302-5c92-47f7-ba73-655a13fea147" src="/sites/default/files/inline-images/Main%20blog%20-%201140x300_36.png" width="2280" height="600" loading="lazy" /></p> <p>In May 2021, a group of researchers at the Ruhr University Bochum (RUB) published an <a href="https://doi.ieeecomputersociety.org/10.1109/SP40001.2021.00110">IEEE symposium paper</a> on problems with certification signatures in PDF. The authors’ findings are summarized on the <a href="https://pdf-insecurity.org/signature/certification.html">PDF Insecurity website</a>.</p> <p>Since iText’s products are often used as part of digital signing workflows (including to create certification signatures), we felt compelled to comment.</p> <p>In this blog post, we’ll take you through some background on these new attacks, and explain what you can do about them.</p> <h2><strong>Background and scope of the exploits</strong></h2> <p>Besides “regular” digital signatures, PDF also allows so-called <em>certification signatures</em>. Cryptographically, those are no different: from a mathematical point of view, a regular signature and a certification signature are equally secure. However, the way PDF stores and interprets these signatures is special:</p> <ul><li aria-level="1">A certification signature must be the first signature in the document. As such, it is often created by the document author, and there can be at most one in any given document.</li> <li aria-level="1">A certification signature contains instructions explaining what changes are allowed after signing.</li> <li aria-level="1">If the certification signature allows it, the document may contain several other regular signatures in addition to the certification signature. These then function as approval signatures.</li> </ul><p>The cryptographically informed reader will ask: “But wait, what’s with all this talk about changes to the file after signing? The whole point of a digital signature is to make that impossible, right?”. That’s certainly true, but there’s a catch: PDF has an incremental update mechanism. The content of a PDF file can be changed by incrementally appending information to the file. Such updates do not change any of the signed data that was already there; in particular, incremental updates can be used in this way to add multiple digital signatures to a PDF document.</p> <p>Processing these updates and judging whether or not they constitute legitimate changes is the validator’s job, and certification signatures provide some input to that process. The enforcement mechanism is contained in the signature’s <em>DocMDP permission level</em>. “DocMDP” stands for “<strong>Doc</strong>ument <strong>M</strong>odification <strong>D</strong>etection and <strong>P</strong>revention”. The DocMDP level is a number between 1 and 3 that is part of the signed data, and hence can’t be tampered with. These are the DocMDP permission levels defined in the standard:</p> <ul><li aria-level="1"><strong>Level 1</strong>: No changes allowed (with some exceptions for PAdES-related data)</li> <li aria-level="1"><strong>Level 2</strong>: Signing and form filling are allowed, as is the instantiation of page templates (if the original signed revision had any)</li> <li aria-level="1"><strong>Level 3</strong>: In addition to the above, comments &amp; annotations are also allowed</li> </ul><p>The exploits that this blog post is about mostly target levels 2 and 3. They largely rely on exploiting viewer behavior and assumptions about incremental update validation. In that sense they do not really attack the PDF standard as such. PDF libraries like iText are also not directly exposed.</p> <p>However, one could argue that that obscures the <em>real</em> problem: the PDF standard doesn’t contain any precise validation requirements for incremental updates; the vague guidance provided by the DocMDP levels is all viewers have to go on. The inevitable consequence of that is that every implementer interprets the rules in their own way. To achieve interoperability in the absence of a standard, they try to be tolerant of other implementations, which ultimately leads to attack vectors of this type. In a way, it’s another example of <a href="https://en.wikipedia.org/wiki/Robustness_principle">Postel’s law</a> gone <a href="https://datatracker.ietf.org/doc/html/draft-thomson-postel-was-wrong-03">wrong</a>.</p> <p>There are ongoing efforts going on at ETSI and at the PDF Association to deal with issues related to DocMDP and incremental updates. Hopefully, these discoveries (and others like it) will strengthen the industry’s mandate to properly standardize incremental update validation in PDF.</p> <h2><strong>The Evil Annotation Attack (EAA)</strong></h2> <p>At its core, the Evil Annotation Attack is not really an exploit, since the behavior it exposes is in fact intended. Nevertheless, it serves as a good reminder for why DocMDP level 3 is dangerous, and why it should be used sparingly! In particular, you should <em>never</em> use DocMDP level 3 to sign a contract.</p> <p>Essentially, PDF has a few annotation types that can be hard to distinguish from regular document content, such as FreeText and Stamp. Given a certified document that allows annotations to be manipulated after the fact, an attacker can significantly alter the appearance of the document that’s presented to the user.</p> <p>Moreover, by cleverly manipulating some of the annotations’ metadata values, some viewers can even be tricked into rendering the annotations without listing them in the viewer’s annotation panel. This makes evil annotation attacks even easier to pull off.</p> <p>You can protect your documents against this type of exploit by avoiding DocMDP level 3 in certification signatures. There are very few legitimate use cases for allowing commenting on a signed document; chances are that you’ll never need the flexibility that DocMDP level 3 provides.</p> <h2><strong>The Sneaky Signature Attack (SSA)</strong></h2> <p>The sneaky signature attack is a more subtle variant of the Evil Annotation Attack. To explain the underlying principle, we need to take a step back and review how PDF signatures work.</p> <p>In PDF, signatures are stored in signature form fields. Form fields in PDF are bound to a particular place in the document by means of a widget annotation. The appearance stream of a widget annotation is what displays the form field on the page. Signature fields are no exception to this rule (although things get a bit murky with invisible annotations—more on those later).</p> <p>Most PDF viewers will allow a signer to create their own signature field when they add a signature to an existing document. As such, most viewers will not flag such a change as suspicious if either of the following conditions are met:</p> <ul><li aria-level="1">the document does not contain a certification signature (i.e., all signatures are “regular” signatures); or</li> <li aria-level="1">the document’s certification signature has a DocMDP level of at least 2.</li> </ul><p>This is perfectly fine in the vast majority of situations, but for documents with a certification signature, it ignores the fact that the original author has no control over where the new signature fields would go! In particular, by maliciously manipulating the widget annotation’s appearance stream, an attacker can mount an Evil Annotation Attack at DocMDP level 2 instead of 3 in sufficiently naive viewers. This is the essence of the Sneaky Signature Attack.</p> <p>To make matters worse, some viewers don’t even require the attacker to create an actual digital signature—an empty signature field already does the trick.</p> <p>If you’re producing documents with a certification signature that don’t require any subsequent modifications, setting the DocMDP level to 1 should effectively make this attack vector unexploitable. However, if your workflow <em>does</em> require form-filling or additional signatures by others, there’s no fool-proof way to protect yourself against this kind of attack. In the end, it’ll be up to the viewer to correctly adjudicate the changes.</p> <h2><strong>Robust certification: some tips and tricks</strong></h2> <p>The attacks discussed here mostly target faulty viewer behavior. As a producer of signed documents, you usually won’t be able to guarantee that your users’ PDF viewers will respond to exploits appropriately. That being said, there are a number of things that you can do to mitigate your exposure.</p> <p>The section titled “VIII.B Short-term Countermeasures” in the <a href="https://doi.ieeecomputersociety.org/10.1109/SP40001.2021.00110">article</a> from the RUB researchers has some useful pointers as well, so make sure to take a look. There’s a download link on the <a href="https://pdf-insecurity.org/downloads/paper_reports_theses.html">authors’ website</a>.</p> <h3><strong><em>Tip 1: Choose the lowest appropriate DocMDP level for your certification signature.</em></strong></h3> <p>It’s not uncommon to see certification signatures with DocMDP level 2 or 3 in situations where those aren’t appropriate. If your workflows don’t require documents to be updated at all after signing, simply set the DocMDP level to 1. DocMDP level 2 has its uses, but if you find yourself using DocMDP level 3 a lot, you might want to reconsider your post-signing workflow.</p> <p>DocMDP level 1 should neuter both EAA and SSA exploits, and EAA can already be thwarted by DocMDP level 2.</p> <h3><strong><em>Tip 2: Fully prepare your document before applying the certification signature</em></strong></h3> <p>In general, you want to restrict the work that future signers/form-fillers have to do to the absolute minimum.</p> <p>Taking the following precautions should help:</p> <ul><li aria-level="1">Create all form fields for future (visible) signatures before certifying the document. This makes it so that future signers never have to create new fields for a visual signature; they can just use one of the existing ones. If sufficiently many people do things the right way, validators will eventually be able to apply stricter criteria as well.</li> <li aria-level="1">If your form fields require tagging, make sure that all fields are registered in the structure tree before signing. Validating updates to the structure tree is difficult and error-prone.</li> </ul><h3><strong><em>Tip 3: If you’re a validator, be strict by default</em></strong></h3> <p>Attacks like these demonstrate once more that strictness in validation is a good thing. For both interactive and non-interactive validators that are capable of incremental update analysis, it arguably makes sense to—by default—forbid creating visible signature fields after a certifying signature, regardless of the DocMDP level.</p> <p>There are two notable scenarios <em>not</em> covered by this rule of thumb:</p> <ul><li aria-level="1">Creating new <em>invisible</em> signature fields should always be allowed. This is necessary to support PAdES-LTA, for example. Since invisible signature fields don’t affect the presentation of the document, they’re harmless for the purposes of these attacks.</li> <li aria-level="1">It’s probably also inadvisable to reject new signature fields created after a previous signature in a non-certified document. This is a matter of pragmatism: many freeware viewers only allow users to create regular, non-certifying signatures, and also don’t allow the user to create their own form fields. Being too strict on signature field creation would hurt those users.</li> </ul><h3><strong><em>Tip 4: If you’re a user, exercise due diligence</em></strong></h3> <p>If you find yourself signing a PDF or validating a signature as an end user, there are a few things to be on the lookout for.</p> <ul><li aria-level="1">If you’re adding a certification signature to the document, make sure you keep the advice of Tips 1 and 2 in mind.</li> <li aria-level="1">If the PDF has a certification signature already, carefully review the permission settings. If your viewer tells you something along the lines of “commenting is allowed”, that’s a red flag. Think twice before you sign!</li> <li aria-level="1">When validating a PDF signature, many viewers are capable of rendering the originally signed revision, as an option in the signature panel. If you want to make sure that what you’re seeing is what the signer actually signed, don’t hesitate to make use of that feature!</li> </ul><p>Complicating matters further, some viewers (including Adobe Acrobat) display permissions for non-certification signatures in the same way as a DocMDP level 3 certification signature. This is unnecessarily confusing, but shouldn’t be cause for alarm <em>per se</em>. Chances are that the signer never intended it that way.</p> <h2><strong>Conclusion</strong></h2> <p>The Evil Annotation and Sneaky Signature Attacks cleverly exploit viewer behavior to mislead users about the content of a document with a certification signature. While the onus is on vulnerable viewers to do something about that, library creators and users can contribute to this process by following best practices. If enough signers follow suit, then validators can also afford to be more strict, which benefits everyone.</p> <p>In addition to that, these exploits serve as a reminder to the PDF industry that we need to invest in the standardization of incremental update validation.</p> <h2><strong>Further reading</strong></h2> <ul><li aria-level="1"><a href="https://pdf-insecurity.org/">pdf-insecurity.org</a>, a website dedicated to PDF-related vulnerabilities discovered by the RUB researchers.</li> <li aria-level="1">The <a href="https://doi.ieeecomputersociety.org/10.1109/SP40001.2021.00110">IEEE symposium paper</a> on these attacks, also available <a href="https://pdf-insecurity.org/downloads/paper_reports_theses.html">here</a>.</li> <li aria-level="1">Our blog post series about the shadow attacks announced by the RUB team in 2020:</li> <li aria-level="1"><a href="https://itextpdf.com/en/blog/technical-notes/investigating-pdf-shadow-attacks-what-are-shadow-attacks-part-1">Part 1</a></li> <li aria-level="1"><a href="https://itextpdf.com/en/blog/technical-notes/investigating-pdf-shadow-attacks-depth-pdf-security-using-itext-part-2">Part 2</a></li> <li><a href="https://itextpdf.com/en/blog/technical-notes/investigating-pdf-shadow-attacks-depth-pdf-security-using-itext-part-3">Part 3</a></li> </ul> </div> <div class="field field--name-field-tags field--type-entity-reference field__items"> <div class="field__label">Tags</div> <a href="/tags/digital-signatures" property="schema:about" hreflang="en">Digital signatures</a> <a href="/tags/certificates" property="schema:about" hreflang="en">Certificates</a> <a href="/tags/security" property="schema:about" hreflang="en">security</a> <a href="/tags/pades" property="schema:about" hreflang="en">PAdES</a> <a href="/tags/authentication" property="schema:about" hreflang="en">Authentication</a> </div> <span class="a2a_kit a2a_kit_size_25 addtoany_list" data-a2a-url="https://itextpdf.com/blog/itext-news-technical-notes/attacks-pdf-certification-and-what-you-can-do-about-them" data-a2a-title="Attacks on PDF certification, and what you can do about them"><a class="a2a_button_facebook"><i class="fa-brands fa-facebook-f fa-2x"></i></a><a class="a2a_button_twitter"><i class="fa-brands fa-twitter fa-2x"></i></a><a class="a2a_button_linkedin"><i class="fa-brands fa-linkedin-in fa-2x"></i></a><a class="a2a_button_whatsapp"><i class="fa-brands fa-whatsapp fa-2x"></i></a><a class="a2a_button_email"><i class="fa-solid fa-envelope fa-2x"></i></a></span> <div class="field field--name-field-article-type field--type-entity-reference field__items"> <div class="field__label">Article type</div> <a href="/blog-type/itext-news" hreflang="en">iText news</a> <a href="/blog-type/technical-notes" hreflang="en">Technical notes</a> </div> <div class="field field--name-field-related-products field--type-entity-reference field__items"> <div class="field__label">Related products</div> <a href="/products/itext-core" hreflang="en">iText Core</a> <a href="/products/itext-suite" hreflang="en">iText Suite</a> </div> <div class="field field--name-field-main-image field--type-entity-reference field__items"> <div class="field__label">Main image</div> <a href="/resources/media/images/pdf-under-attack" hreflang="en">PDF under attack</a> </div> <div class="field field--name-field-promoted-to-home-page-text field--type-string field__items"> <div class="field__label">Promoted to home page text</div> Read our latest blogpost: Attacks on PDF certification, and what you can do about them </div> Wed, 23 Jun 2021 12:56:35 +0000 julie.kluyskens 15196 at https://itextpdf.com Israel’s Green Pass PDF Vaccination Certificate contains security flaws https://itextpdf.com/blog/itext-news/israels-green-pass-pdf-vaccination-certificate-contains-security-flaws <span property="schema:name" class="field field--name-title field--type-string field--label-hidden">Israel’s Green Pass PDF Vaccination Certificate contains security flaws</span> <span rel="schema:author" class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/users/rienverbruggh" typeof="schema:Person" property="schema:name" datatype="">Rien.Verbruggh</span></span> <span property="schema:dateCreated" content="2021-03-04T08:30:00+00:00" class="field field--name-created field--type-created field--label-hidden"><time datetime="2021-03-04T09:30:00+01:00" title="Thursday, March 4, 2021 - 09:30" class="datetime">Thu, 03/04/2021 - 09:30</time> </span> <div class="field field--name-field-tags field--type-entity-reference field__items"> <div class="field__label">Tags</div> <a href="/tags/certificates" property="schema:about" hreflang="en">Certificates</a> <a href="/tags/digital-signatures" property="schema:about" hreflang="en">Digital signatures</a> <a href="/tags/digital-id" property="schema:about" hreflang="en">digital id</a> <a href="/tags/green-pass" property="schema:about" hreflang="en">green pass</a> <a href="/tags/vaccination-certificate" property="schema:about" hreflang="en">vaccination certificate</a> <a href="/tags/covid-19" property="schema:about" hreflang="en">covid-19</a> <a href="/tags/israel" property="schema:about" hreflang="en">israel</a> <a href="/tags/europe" property="schema:about" hreflang="en">europe</a> </div> <span class="a2a_kit a2a_kit_size_25 addtoany_list" data-a2a-url="https://itextpdf.com/blog/itext-news/israels-green-pass-pdf-vaccination-certificate-contains-security-flaws" data-a2a-title="Israel’s Green Pass PDF Vaccination Certificate contains security flaws"><a class="a2a_button_facebook"><i class="fa-brands fa-facebook-f fa-2x"></i></a><a class="a2a_button_twitter"><i class="fa-brands fa-twitter fa-2x"></i></a><a class="a2a_button_linkedin"><i class="fa-brands fa-linkedin-in fa-2x"></i></a><a class="a2a_button_whatsapp"><i class="fa-brands fa-whatsapp fa-2x"></i></a><a class="a2a_button_email"><i class="fa-solid fa-envelope fa-2x"></i></a></span> <div class="field field--name-field-article-type field--type-entity-reference field__items"> <div class="field__label">Article type</div> <a href="/blog-type/itext-news" hreflang="en">iText news</a> </div> <!-- strip all div's as they break the logic behind brick--no-vertical-padding --> <div> <div class="paragraph paragraph--text-area paragraph--text-area--default"> <h2></h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <p><img alt="green pass pdf vaccination certificate" data-entity-type="file" data-entity-uuid="54d8eb78-73ce-4cf4-add0-e473378d939b" src="/sites/default/files/inline-images/DS%20fin-01_1.jpg" width="4750" height="1250" loading="lazy" /></p> <figure role="group" class="caption caption-img align-right"><img alt="Israeli Health Ministry Vaccination Certificate example" data-entity-type="file" data-entity-uuid="d5bea010-909d-426a-931e-233ff7618a06" src="/sites/default/files/inline-images/israele%20green%20pass%20example.png" width="582" height="809" loading="lazy" /><figcaption>Israeli Health Ministry Vaccination Certificate example</figcaption></figure><p>Israel recently introduced its “Green Pass” vaccination certificate, which is a PDF document that serves as proof of that the holder has been vaccinated against coronavirus, or has recovered from the disease with presumed immunity. As NBC <a href="https://www.nbcnews.com/news/world/green-pass-israel-s-covid-19-vaccination-certificate-opens-fast-n1258467">reported</a>, with the government claiming that COVID-19 vaccines have been administered to almost half the population, this has led to the reopening of swathes of Israel’s economy, and the start of a return to routine.</p> <h2>400,000 Israelis have already downloaded the vaccination certificate</h2> <p>A source from Israeli Ministry of Health reported that over 400,000 Israelis have already downloaded the vaccination certificate. It is a document resembling an ID card and contains a photo, date of vaccination and a unique QR code which can be scanned to prove the authenticity of the physical document. In the future, it is also expected to allow access to certain venues.</p> </div> </div> <div class="paragraph paragraph--text-area paragraph--text-area--default"> <h2> <div id="&quot;Green-Pass&quot;-makes-cyber-security-experts-naseous" class="anchor field field--name-field-section-heading field--type-string field__items"> &quot;Green Pass&quot; makes cyber security experts naseous </div> </h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <figure role="group" class="caption caption-img align-right"><img alt="chec point video screencapture" data-entity-type="file" data-entity-uuid="4e95f6fe-0f25-4250-a4e8-91665499e687" src="/sites/default/files/inline-images/Check%20Point%20video%20screencapture.png" width="350" height="203" loading="lazy" /><figcaption>Credit: Check Point (screen capture)</figcaption></figure><p>However, Israeli cybersecurity firm Check Point found a critical flaw with the vaccination certificate. As demonstrated in a Facebook <a href="https://fb.watch/3S2vZC-l_Y/">video</a>, it is astonishingly easy for anyone with access to Photoshop or Acrobat, or indeed any image or visual document editing program, to create a seemingly valid document indicating they have received the vaccine and are immune to the virus.</p> <p><em>Watch video from </em><a href="https://fb.watch/3S2vZC-l_Y/"><em>https://fb.watch/3S2vZC-l_Y/</em></a></p> <p>The video above has Hebrew subtitles, but the process of faking a document is pretty clear and extremely simple. As Ran Bar-Zik, an expert on cybersecurity, <a href="https://www.facebook.com/rbarzik/posts/3872539906117567">wrote</a> on Facebook “It is easy with a graphics program to change the text on the pass, but the QR code is what looks scary and hard to forge, no? Actually, this is very easy,” He went on to explain that the QR code has <strong>no encryption at all</strong>, and corresponds directly to a text string, with the holder’s personal information, including name, ID number, and date of vaccination, just as printed on the pass itself.</p> </div> </div> <article class="paragraph paragraph--call-to-action paragraph--call-to-action--default"> <h2>Questions about digitally signing PDF documents or certificates?</h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-paragraph-text field--type-text-long field__items"> <p>You can learn from the experiences of others to avoid repeating their mistakes. Help iText understand your specific situation and digital signing challenges with PDF certificates or documents, and our experts can advise you on how to proceed without making similar errors.</p> </div> <div class="field field--name-field-cta-template field--type-list-string field__items"> <div class="field__label">CTA template</div> Wide blue </div> <a href="https://itextpdf.com/en/questions-about-digitally-signing-your-pdf-certificates-itext-7" class="button button--wide button--chevron"> <span> Ask an expert about digitally signing your PDF certificates with iText 7 <svg aria-hidden="true" class="chevron-right" viewBox="0 0 9 12" xmlns="http://www.w3.org/2000/svg"><path d="M.84 10.59L5.42 6 .84 1.41 2.25 0l6 6-6 6z"></path></svg> </span> </a> </article> <div class="paragraph paragraph--text-area paragraph--text-area--default"> <h2> <div id="Digital-signing-done-right-with-iText" class="anchor field field--name-field-section-heading field--type-string field__items"> Digital signing done right with iText </div> </h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <p>Using a world-renowned PDF library like <a href="https://itextpdf.com/en/products/itext-7/itext-7-core">iText 7 Core</a> could have made the generation and <a href="https://itextpdf.com/en/solutions/digital-signatures-pdf-documents">digital signing</a> (or implementing other <a href="https://itextpdf.com/en/solutions/pdf-security">PDF security measures</a>) of 400,000+ green passes a cinch. If you need to do something similar you can check out our free ebook “<a href="https://itextpdf.com/en/resources/books/digital-signatures-pdf">Digital signatures for PDF documents</a>” and discover plenty of <a href="https://kb.itextpdf.com/home/it7kb/search?q=&amp;l=digital_signatures&amp;max=500">digital signature examples on the iText Knowledge Base</a>.</p> <p><a href="https://www.youtube.com/watch?v=0VX66NUoBRo&amp;t=91s&amp;ab_channel=iText">Watch our webinar on Digital Signatures - How does digital signing work in PDFs?</a></p> <p>Check Point explained to ministry officials, “the current certificate validating vaccination is just a file that can be easily edited by any program that can edit PDFs. To forge it, all an attacker needs to do is create a new and unique barcode - and there are plenty of sites that do this - and graft it onto an existing document with a program and thus create a seemingly valid certificate of vaccination.”</p> <p>Indeed, the video shows how it can be achieved in barely a minute, using two websites to generate a new QR code and create the fake document. Scanning the specially created QR code would lead to a copy of the falsified image, thus seemingly confirming its validity.</p> <p>There is already a thriving black market on Telegram, with fake vaccination certificates being offered for around 230 USD.</p> <p>As <a href="https://www.haaretz.com/israel-news/tech-news/.premium-anyone-can-fake-israel-s-vaccination-certificate-here-s-how-1.9537465">mentioned in Haaretz</a>, Israel’s longest-running newspaper, there were other, better ways of doing this:</p> <blockquote> <p>Cryptographers said simple solutions could be quickly implemented to secure the document. For example, they note using a unique “digital signature” (as opposed to just a scannable code) which would be scannable and could serve as a way to validate the originality of the document. </p> </blockquote> <p>In response to Check Point’s findings, the Health Ministry stated they were aware of the issue, and that it, and other issues were being addressed.</p> <h2>Digital signing of PDF documents or certificates</h2> <p>Ticket vendors often use HMAC-based tokens to make tickets harder to forge. These tokens are built using symmetric cryptography: they require the issuer and the verifier to share a secret ahead of time. Such an approach is perfectly fine if the verifying party is the same (or closely related to) the issuing party. However, if the relationship between the parties is an asymmetric one, relying on HMAC alone is dangerous, since anyone with the ability to verify tokens can also <em>create</em> valid tokens!</p> <p>Israel's Green Pass falls into the latter category: since these passes are intended to be used for admittance to venues, venue operators also need to be able to verify their authenticity. On the other hand, it's probably safe to say that the Israeli government wouldn't want them to be able to produce their own Green Passes.</p> <p>Situations like this call for a solution based on public-key cryptography, which is supported in PDF digital signatures. A digitally signed PDF file can be validated by anyone, without giving them the ability to forge their own copies. Attaching a digital signature to a printed document is also easy: it suffices to include a QR code linking back to a signed electronic copy.</p> <p><strong>Note:</strong> Digital signatures don't work on printed documents (since paper isn’t digital), unless there's a QR code linking back to the signed PDF document.</p> <h2>Will Europe make the same mistake?</h2> <p>EU leaders are currently looking into the adoption of a common approach on the issue, diplomatic sources said. As <a href="https://www.euractiv.com/section/coronavirus/news/eu-commission-cautious-with-unilateral-moves-on-vaccination-passports/">Euractiv reports</a>.</p> <blockquote> <p>In an attempt to roll back travel restrictions currently in place across Europe, Austrian Chancellor Sebastian Kurz has proposed to introduce a “green pass” for those who do not pose a health risk.</p> <p> </p> <p>According to Kurz, three categories of people would be granted such a pass: those who already received the vaccine, those who have already been infected, and those who have been tested very recently.</p> </blockquote> <p><a href="https://www.dw.com/en/eu-coronavirus-summit-vaccine-certificates-expected-by-summer/a-56701869">Deutsche Well reported</a> on the last EU coronavirus summit</p> <blockquote> <p><em>Vaccine certificates expected by summer. European Union leaders have met virtually to hash out coronavirus issues. Progress was promised on vaccines, though questions remain over border closings and travel</em></p> </blockquote> </div> </div> <div class="paragraph paragraph--text-area paragraph--text-area--default"> <h2> <div id="Governments-issue-PDF-certificates-generated-with-iText-DITO" class="anchor field field--name-field-section-heading field--type-string field__items"> Governments issue PDF certificates generated with iText DITO </div> </h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <p><a href="https://itextpdf.com/en/products/itext-dito">iText DITO</a>, the data-driven, template-based PDF generator simplifies the process of creating and maintaining PDFs. This tool has been successfully used by other government agencies before for PDF certification creation (without being digitally signed). The DABS (Databank voor de Akten van Burgerlijke Stand or Database of Civil Registry Records) project was initiated by the Belgian government and they use iText DITO® to <a href="https://itextpdf.com/en/resources/case-studies/itext-dito-powering-digital-future-civil-records-belgium">revolutionize citizen certification</a> inside (and outside) Belgium. This cooperative project between the Department of Justice, Department of Internal Affairs (including the National Register), Department of Foreign Affairs, Municipalities and Consulates and many other government departments is an initiative that has been coordinated by the Dienst Administratieve Vereenvoudiging (DAV) or Administrative Simplification Service.</p> </div> </div> <article class="paragraph paragraph--call-to-action paragraph--call-to-action--default"> <h2>COVID-19 Passenger Locator Forms</h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-paragraph-text field--type-text-long field__items"> <p>Of course, it’s not just digital signing of PDF documents which can be achieved with iText, but also prefilling, data extraction and processing of PDF forms are tasks which are easily accomplished by using iText 7.</p> </div> <a href="https://itextpdf.com/en/blog/itext-news/passenger-locator-forms-digital-document-workflow-urgency-during-covid-19" class="button button--wide button--chevron"> <span> Passenger Locator Forms: a digital document workflow urgency during COVID-19 <svg aria-hidden="true" class="chevron-right" viewBox="0 0 9 12" xmlns="http://www.w3.org/2000/svg"><path d="M.84 10.59L5.42 6 .84 1.41 2.25 0l6 6-6 6z"></path></svg> </span> </a> </article> <article class="paragraph paragraph--call-to-action paragraph--call-to-action--default"> <h2>Questions about digitally signing your PDF certificates with iText 7?</h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-paragraph-text field--type-text-long field__items"> <p>You can learn from the experiences of others. By sharing these lessons learned you can avoid the same mistake all over again. Help iText understand your specific situation and digital signing challenge with PDF certificates or documents and our experts will advice you on how to proceed.</p> </div> <div class="field field--name-field-cta-template field--type-list-string field__items"> <div class="field__label">CTA template</div> Wide blue </div> <a href="https://itextpdf.com/en/questions-about-digitally-signing-your-pdf-certificates-itext-7" class="button button--wide button--chevron"> <span> Ask an expert about digitally signing your PDF certificates with iText 7 <svg aria-hidden="true" class="chevron-right" viewBox="0 0 9 12" xmlns="http://www.w3.org/2000/svg"><path d="M.84 10.59L5.42 6 .84 1.41 2.25 0l6 6-6 6z"></path></svg> </span> </a> </article> <div class="paragraph paragraph--text-area paragraph--text-area--default"> <h2></h2> <div class="clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <style type="text/css"> /*<![CDATA[*/ .cta.blue {padding: 25px;} /*]]>*/ </style> </div> </div> </div> <div class="field field--name-field-related-products field--type-entity-reference field__items"> <div class="field__label">Related products</div> <a href="/products/itext-core" hreflang="en">iText Core</a> <a href="/products/itext-dito" hreflang="en">iText DITO® </a> </div> <div class="field field--name-field-main-image field--type-entity-reference field__items"> <div class="field__label">Main image</div> <a href="/resources/media/images/green-pass-pdf-vaccination-certificate-0" hreflang="en">green pass pdf vaccination certificate</a> </div> Thu, 04 Mar 2021 08:30:00 +0000 Rien.Verbruggh 14886 at https://itextpdf.com Digitally sign a PDF using AWS KMS and iText https://itextpdf.com/blog/technical-notes/using-itext-and-aws-kms-digitally-sign-pdf-document <span property="schema:name" class="field field--name-title field--type-string field--label-hidden">Digitally sign a PDF using AWS KMS and iText</span> <span rel="schema:author" class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/users/ianmorris" typeof="schema:Person" property="schema:name" datatype="">ian.morris</span></span> <span property="schema:dateCreated" content="2021-01-29T13:10:15+00:00" class="field field--name-created field--type-created field--label-hidden"><time datetime="2021-01-29T14:10:15+01:00" title="Friday, January 29, 2021 - 14:10" class="datetime">Fri, 01/29/2021 - 14:10</time> </span> <div property="schema:text" class="wysiwyg-content clearfix text-formatted field field--name-body field--type-text-with-summary field__items"> <p><img alt="Using iText and AWS KMS to digitally sign a PDF document" data-entity-type="file" data-entity-uuid="bfa65b14-5399-4c21-9206-47bebca99327" src="/sites/default/files/inline-images/Blog%20banner%201140x300_Using%20iText%20and%20AWS%20KMS%20to%20digitally%20sign%20a%20PDF%20document.png" width="1140" height="300" loading="lazy" /></p> <h2 id="UsingiText7andAWSKMStodigitallysignaPDFdocument-Introduction">Introduction</h2> <p><em><strong>Note: This article was originally written for iText Core version 7.1.x. However, the text and code examples have been revised to account for the API changes and improvements to digital signing we introduced in iText Core version 8.</strong></em></p> <p>Here at iText we’ve long been involved with PDF digital signatures. We first published our <a href="https://itextpdf.com/en/resources/books/digital-signatures-pdf" rel="nofollow">digital signatures eBook</a> back in 2013, which provided a comprehensive overview of PDF features, industry standards and technology options relating to secure digital signatures, together with in-depth best practices, real-life examples, and code samples for PDF development.</p> <p>Since then, we’ve continued to promote the technology for secure PDF documents, as it provides integrity, authenticity, non-repudiation, and assurance of when a document was signed. We’ve also kept pace with advances in the field, supporting the PAdES framework and PDF 2.0, and updating our <a href="https://itextpdf.com/en/resources/books/addendum-digital-signatures-pdf-documents" rel="nofollow">Java and C# (.NET) code examples</a> to apply to the latest versions of iText.</p> <p>An essential component in creating a secure digital signature is the generation of an asymmetric key pair, consisting of both a public and a private key. There are a number of ways to generate such a key pair, but one of the most secure is the use of a hardware security module (or HSM). This is a physical computing device and is usually very expensive.</p> <h2 id="UsingiText7andAWSKMStodigitallysignaPDFdocument-HereComesaNewChallenger">Here Comes a New Challenger</h2> <p>However, Amazon Web Services now offers the generation of asymmetric keys as part of its Key Management Service (KMS) which makes it easy to create and manage cryptographic keys and control their use across a range of AWS services and in your applications. Similar to the symmetric key features that were previously available, asymmetric keys can be generated as customer master keys (CMKs) where the private portion never leaves the service, or as a data key where the private portion is returned to your calling application encrypted under a CMK.</p> <p>Since it’s a scalable service with no upfront charges, AWS KMS can be an attractive option for digitally signing PDFs. It’s not all plain sailing though. Since AWS KMS doesn’t store or associate digital certificates with asymmetric CMKs it creates, it’s not <em>directly</em> possible to use the asymmetric CMK for signing PDFs, as you would first have to generate a certificate for the public key of your AWS KMS signing key pair.</p> <p>This topic came up in a recent Stack Overflow question, and the comprehensive answer provided by Michael Klink led to this article which we hope many of you will benefit from. We’ll walk through the whole process of accessing the AWS KMS API to generate a digital signature, and then applying that signature to a PDF with iText. In addition, we’ll also point out some things you’ll need to consider if you plan to do mass-signing operations with AWS KMS.</p> <p>Of course, Amazon is not the only big player in cloud services, and so it should not be surprising that Google and Microsoft also provide similar functionality. Google has its <a href="https://cloud.google.com/security-key-management" rel="nofollow">Cloud Key Management</a> and Microsoft Azure offers their <a href="https://azure.microsoft.com/en-us/services/key-vault/" rel="nofollow">Key Vault</a>, both of which lower the cost of entry to using HSMs for cryptographic key management. While we won’t be covering them in this article, the process of signing a PDF using these services should be largely the same.</p> <p> Once again, we’ve worked with independent PDF expert and top StackOverflow contributor Michael Klink (<a href="https://stackoverflow.com/users/1729265/mkl" rel="nofollow">@mkl</a>) who kindly provided C# versions of his Java code examples from his original answer for our .NET users. Each code snippet included in this article has a link leading to the full Java/C# example on our Knowledge Base.</p> <h2 id="UsingiText7andAWSKMStodigitallysignaPDFdocument-Differentstrokesfordifferentfolks">Different strokes for different folks</h2> <p>A quick note before we continue. While the <a href="https://api.itextpdf.com/iText/java/latest/" rel="nofollow">Java</a> and <a href="https://api.itextpdf.com/iText/dotnet/latest/" rel="nofollow">C#</a> iText Core APIs are largely the same, there are a number of differences in the code examples here, in part due to certain differences in the Java and .NET <a href="https://docs.aws.amazon.com/kms/latest/APIReference/Welcome.html" rel="nofollow">AWS KMS APIs</a>. For example, the latter API uses the <code>async</code> pattern for .NET.</p> <p>In addition, genuine .NET classes were used for the creation of the self-signed certificate and there are some differences between the BouncyCastle Java and .NET APIs. So, the differences in the code are not just in its method name capitalization...</p> <p>Don’t worry though, as we’ll be pointing out these differences where they occur throughout the article</p> </div> <div class="field field--name-field-tags field--type-entity-reference field__items"> <div class="field__label">Tags</div> <a href="/tags/aws" property="schema:about" hreflang="en">AWS</a> <a href="/tags/kms" property="schema:about" hreflang="en">KMS</a> <a href="/tags/cloud" property="schema:about" hreflang="en">Cloud</a> <a href="/tags/digital-signatures" property="schema:about" hreflang="en">Digital signatures</a> <a href="/tags/certificates" property="schema:about" hreflang="en">Certificates</a> <a href="/tags/encryption" property="schema:about" hreflang="en">encryption</a> <a href="/tags/signing" property="schema:about" hreflang="en">signing</a> <a href="/tags/hsm" property="schema:about" hreflang="en">HSM</a> <a href="/tags/itext-8" property="schema:about" hreflang="en">iText 8</a> </div> <span class="a2a_kit a2a_kit_size_25 addtoany_list" data-a2a-url="https://itextpdf.com/blog/technical-notes/using-itext-and-aws-kms-digitally-sign-pdf-document" data-a2a-title="Digitally sign a PDF using AWS KMS and iText"><a class="a2a_button_facebook"><i class="fa-brands fa-facebook-f fa-2x"></i></a><a class="a2a_button_twitter"><i class="fa-brands fa-twitter fa-2x"></i></a><a class="a2a_button_linkedin"><i class="fa-brands fa-linkedin-in fa-2x"></i></a><a class="a2a_button_whatsapp"><i class="fa-brands fa-whatsapp fa-2x"></i></a><a class="a2a_button_email"><i class="fa-solid fa-envelope fa-2x"></i></a></span> <div class="field field--name-field-article-type field--type-entity-reference field__items"> <div class="field__label">Article type</div> <a href="/blog-type/technical-notes" hreflang="en">Technical notes</a> </div> <!-- strip all div's as they break the logic behind brick--no-vertical-padding --> <div> <div class="paragraph paragraph--text-area paragraph--text-area--default"> <h2></h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <h3 id="UsingiText7andAWSKMStodigitallysignaPDFdocument-SigningaPdfDocumentusingthedigitalsignaturereturnedbyAWSKMS">Signing a PdfDocument using the digital signature returned by AWS KMS</h3> <p><em>In the context of this article it is assumed that you have stored your credentials in the </em><code><em>default</em></code><em> section of your </em><code><em>~/.aws/credentials</em></code><em> </em><em>file and your region in the </em><code><em>default</em></code><em> </em><em>section of your </em><code><em>~/.aws/config</em></code><em> </em><em>file. Otherwise, you'll have to adapt the </em><code><em>KmsClient</em></code><em> instantiation or initialization in the following code examples.</em></p> </div> </div> <section class="brick jdoodle__brick"> <div class="brick__inner"> <div class="paragraph paragraph--jdoodle paragraph--jdoodle--default"> <h2></h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <h3 id="UsingiText7andAWSKMStodigitallysignaPDFdocument-GeneratingaCertificateforanAWSKMSKeyPair">Generating a Certificate for an AWS KMS Key Pair</h3> <p>As we noted above, AWS KMS signs using a plain asymmetric key pair and it does not provide a X.509 certificate for the public key. However, interoperable PDF signatures require a X.509 certificate for the public key, to establish trust in the signature. Thus, the first step to take for interoperable AWS KMS PDF signing is to generate an X.509 certificate for the public key of your AWS KMS signing key pair.</p> <p>For testing purposes, you can create a self-signed certificate using this Java helper method which is based on code from <a href="https://stackoverflow.com/a/43918337/1729265" rel="nofollow">this stack overflow answer</a>:</p> </div> &lt;div data-pym-src=&quot;https://itext.jdoodle.com/embed/itext&quot; data-java-libs=&quot;com.itextpdf:itext7-core:latest.release,org.slf4j:slf4j-api:1.7.28,org.slf4j:slf4j-simple:1.7.28,com.itextpdf:pdfocr-tesseract4:latest.release&quot; data-csharp-libs=&quot;itext7:latest.release&quot; data-show-upload=&quot;false&quot; data-hide-execute=&quot;true&quot; data-max-uploads=&quot;3&quot; data-max-file-size=&quot;5000000&quot;&gt; &lt;div data-script-type=&quot;java&quot;&gt;&lt;script&gt;public static X509Certificate generateSelfSignedCertificate(String keyId, String subjectDN, Function&lt;List&lt;SigningAlgorithmSpec&gt;, SigningAlgorithmSpec&gt; selector) throws IOException, GeneralSecurityException { long now = System.currentTimeMillis(); Date startDate = new Date(now); X500Name dnName = new X500Name(subjectDN); BigInteger certSerialNumber = new BigInteger(Long.toString(now)); // &lt;-- Using the current timestamp as the certificate serial number Calendar calendar = Calendar.getInstance(); calendar.setTime(startDate); calendar.add(Calendar.YEAR, 1); // &lt;-- 1 Yr validity Date endDate = calendar.getTime(); PublicKey publicKey = null; SigningAlgorithmSpec signingAlgorithmSpec = null; try ( KmsClient kmsClient = KmsClient.create() ) { GetPublicKeyResponse response = kmsClient.getPublicKey(GetPublicKeyRequest.builder().keyId(keyId).build()); SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(response.publicKey().asByteArray()); JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); publicKey = converter.getPublicKey(spki); List&lt;SigningAlgorithmSpec&gt; signingAlgorithms = response.signingAlgorithms(); signingAlgorithmSpec = selector.apply(signingAlgorithms); } JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(dnName, certSerialNumber, startDate, endDate, dnName, publicKey); ContentSigner contentSigner = new AwsKmsContentSigner(keyId, signingAlgorithmSpec); // Extensions -------------------------- // Basic Constraints BasicConstraints basicConstraints = new BasicConstraints(true); // &lt;-- true for CA, false for EndEntity certBuilder.addExtension(new ASN1ObjectIdentifier(&quot;2.5.29.19&quot;), true, basicConstraints); // Basic Constraints is usually marked as critical. // ------------------------------------- return new JcaX509CertificateConverter().setProvider(&quot;BC&quot;).getCertificate(certBuilder.build(contentSigner)); }&lt;/script&gt;&lt;/div&gt; &lt;div data-script-type=&quot;csharp&quot;&gt;&lt;script&gt;public static X509Certificate2 GenerateSelfSignedCertificate(string keyId, string subjectDN, Func&lt;List&lt;string&gt;, string&gt; selector) { string signingAlgorithm = null; using (var kmsClient = new AmazonKeyManagementServiceClient()) { GetPublicKeyRequest getPublicKeyRequest = new GetPublicKeyRequest() { KeyId = keyId }; GetPublicKeyResponse getPublicKeyResponse = kmsClient.GetPublicKeyAsync(getPublicKeyRequest).Result; List&lt;string&gt; signingAlgorithms = getPublicKeyResponse.SigningAlgorithms; signingAlgorithm = selector.Invoke(signingAlgorithms); byte[] spkiBytes = getPublicKeyResponse.PublicKey.ToArray(); CertificateRequest certificateRequest = null; X509SignatureGenerator simpleGenerator = null; string keySpecString = getPublicKeyResponse.CustomerMasterKeySpec.ToString(); if (keySpecString.StartsWith(&quot;ECC&quot;)) { ECDsa ecdsa = ECDsa.Create(); int bytesRead = 0; ecdsa.ImportSubjectPublicKeyInfo(new ReadOnlySpan&lt;byte&gt;(spkiBytes), out bytesRead); certificateRequest = new CertificateRequest(subjectDN, ecdsa, GetHashAlgorithmName(signingAlgorithm)); simpleGenerator = X509SignatureGenerator.CreateForECDsa(ecdsa); } else if (keySpecString.StartsWith(&quot;RSA&quot;)) { RSA rsa = RSA.Create(); int bytesRead = 0; rsa.ImportSubjectPublicKeyInfo(new ReadOnlySpan&lt;byte&gt;(spkiBytes), out bytesRead); RSASignaturePadding rsaSignaturePadding = GetSignaturePadding(signingAlgorithm); certificateRequest = new CertificateRequest(subjectDN, rsa, GetHashAlgorithmName(signingAlgorithm), rsaSignaturePadding); simpleGenerator = X509SignatureGenerator.CreateForRSA(rsa, rsaSignaturePadding); } else { throw new ArgumentException(&quot;Cannot determine encryption algorithm for &quot; + keySpecString, nameof(keyId)); } X509SignatureGenerator generator = new SignatureGenerator(keyId, signingAlgorithm, simpleGenerator); X509Certificate2 certificate = certificateRequest.Create(new X500DistinguishedName(subjectDN), generator, System.DateTimeOffset.Now, System.DateTimeOffset.Now.AddYears(2), new byte[] { 17 }); return certificate; } } public static HashAlgorithmName GetHashAlgorithmName(string signingAlgorithm) { if (signingAlgorithm.Contains(&quot;SHA_256&quot;)) { return HashAlgorithmName.SHA256; } else if (signingAlgorithm.Contains(&quot;SHA_384&quot;)) { return HashAlgorithmName.SHA384; } else if (signingAlgorithm.Contains(&quot;SHA_512&quot;)) { return HashAlgorithmName.SHA512; } else { throw new ArgumentException(&quot;Cannot determine hash algorithm for &quot; + signingAlgorithm, nameof(signingAlgorithm)); } } public static RSASignaturePadding GetSignaturePadding(string signingAlgorithm) { if (signingAlgorithm.StartsWith(&quot;RSASSA_PKCS1_V1_5&quot;)) { return RSASignaturePadding.Pkcs1; } else if (signingAlgorithm.StartsWith(&quot;RSASSA_PSS&quot;)) { return RSASignaturePadding.Pss; } else { return null; } } class SignatureGenerator : X509SignatureGenerator { public SignatureGenerator(string keyId, string signingAlgorithm, X509SignatureGenerator simpleGenerator) { this.keyId = keyId; this.signingAlgorithm = signingAlgorithm; this.simpleGenerator = simpleGenerator; } public override byte[] GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlgorithm) { HashAlgorithmName hashAlgorithmHere = GetHashAlgorithmName(signingAlgorithm); if (hashAlgorithm != hashAlgorithmHere) { throw new ArgumentException(&quot;Hash algorithm &quot; + hashAlgorithm + &quot;does not match signing algorithm &quot; + signingAlgorithm, nameof(hashAlgorithm)); } return simpleGenerator.GetSignatureAlgorithmIdentifier(hashAlgorithm); } public override byte[] SignData(byte[] data, HashAlgorithmName hashAlgorithm) { HashAlgorithmName hashAlgorithmHere = GetHashAlgorithmName(signingAlgorithm); if (hashAlgorithm != hashAlgorithmHere) { throw new ArgumentException(&quot;Hash algorithm &quot; + hashAlgorithm + &quot;does not match signing algorithm &quot; + signingAlgorithm, nameof(hashAlgorithm)); } using (var kmsClient = new AmazonKeyManagementServiceClient()) { SignRequest signRequest = new SignRequest() { SigningAlgorithm = signingAlgorithm, KeyId = keyId, MessageType = MessageType.RAW, Message = new MemoryStream(data) }; SignResponse signResponse = kmsClient.SignAsync(signRequest).Result; return signResponse.Signature.ToArray(); } } protected override PublicKey BuildPublicKey() { return simpleGenerator.PublicKey; } string keyId; string signingAlgorithm; X509SignatureGenerator simpleGenerator; }&lt;/script&gt;&lt;/div&gt; &lt;/div&gt; </div> </div> </section> <section class="brick jdoodle__brick"> <div class="brick__inner"> <div class="paragraph paragraph--jdoodle paragraph--jdoodle--default"> <h2></h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <p><em><a href="https://kb.itextpdf.com/itext/using-itext-7-and-aws-kms-to-digitally-sign-a-pdf-">CertificateUtils helper method</a></em></p> <p>.NET offers its own means for the creation of certificate requests and self-signed certificates, the <code>CertificateRequest</code> class.</p> <p>As with the BouncyCastle implementation in the Java example, this class also has the actual signature creation (for the self-signed certificate) delegated to a helper, which here is an <code>X509SignatureGenerator</code> instance. Obviously, .NET does not have a ready-to-use variant of that class for AWS KMS signing, so we have to provide one ourselves, the inner class <code>SignatureGenerator</code> in the .NET example. Fortunately, we can re-use .NET variants of <code>X509SignatureGenerator</code> for all methods except the actual <code>SignData</code> signing method.</p> <p>Returning to our Java example, the <code>AwsKmsContentSigner</code> class used in the code above is this implementation of the BouncyCastle interface <code>ContentSigner</code>:</p> <pre> <code class="language-java"><div class="geshifilter"><table class="java geshifilter-java"><tbody><tr class="li1"><td class="ln"><pre class="de1">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 </pre></td><td class="de1"><pre class="de1"><span class="kw1">public</span> <span class="kw1">class</span> AwsKmsContentSigner <span class="kw1">implements</span> ContentSigner <span class="br0">{</span>     <span class="kw1">final</span> <span class="kw3">ByteArrayOutputStream</span> outputStream <span class="sy0">=</span> <span class="kw1">new</span> <span class="kw3">ByteArrayOutputStream</span><span class="br0">(</span><span class="br0">)</span><span class="sy0">;</span>     <span class="kw1">final</span> <span class="kw3">String</span> keyId<span class="sy0">;</span>     <span class="kw1">final</span> SigningAlgorithmSpec signingAlgorithmSpec<span class="sy0">;</span>     <span class="kw1">final</span> AlgorithmIdentifier signatureAlgorithm<span class="sy0">;</span>       <span class="kw1">public</span> AwsKmsContentSigner<span class="br0">(</span><span class="kw3">String</span> keyId, SigningAlgorithmSpec signingAlgorithmSpec<span class="br0">)</span> <span class="br0">{</span>         <span class="kw1">this</span>.<span class="me1">keyId</span> <span class="sy0">=</span> keyId<span class="sy0">;</span>         <span class="kw1">this</span>.<span class="me1">signingAlgorithmSpec</span> <span class="sy0">=</span> signingAlgorithmSpec<span class="sy0">;</span>         <span class="kw3">String</span> signatureAlgorithmName <span class="sy0">=</span> signingAlgorithmNameBySpec.<span class="me1">get</span><span class="br0">(</span>signingAlgorithmSpec<span class="br0">)</span><span class="sy0">;</span>         <span class="kw1">if</span> <span class="br0">(</span>signatureAlgorithmName <span class="sy0">==</span> <span class="kw2">null</span><span class="br0">)</span>             <span class="kw1">throw</span> <span class="kw1">new</span> <span class="kw3">IllegalArgumentException</span><span class="br0">(</span><span class="st0">"Unknown signature algorithm "</span> <span class="sy0">+</span> signingAlgorithmSpec<span class="br0">)</span><span class="sy0">;</span>         <span class="kw1">this</span>.<span class="me1">signatureAlgorithm</span> <span class="sy0">=</span> <span class="kw1">new</span> DefaultSignatureAlgorithmIdentifierFinder<span class="br0">(</span><span class="br0">)</span>.<span class="me1">find</span><span class="br0">(</span>signatureAlgorithmName<span class="br0">)</span><span class="sy0">;</span>     <span class="br0">}</span>       @Override     <span class="kw1">public</span> <span class="kw4">byte</span><span class="br0">[</span><span class="br0">]</span> getSignature<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>         <span class="kw1">try</span> <span class="br0">(</span>   KmsClient kmsClient <span class="sy0">=</span> KmsClient.<span class="me1">create</span><span class="br0">(</span><span class="br0">)</span> <span class="br0">)</span> <span class="br0">{</span>             SignRequest signRequest <span class="sy0">=</span> SignRequest.<span class="me1">builder</span><span class="br0">(</span><span class="br0">)</span>                     .<span class="me1">signingAlgorithm</span><span class="br0">(</span>signingAlgorithmSpec<span class="br0">)</span>                     .<span class="me1">keyId</span><span class="br0">(</span>keyId<span class="br0">)</span>                     .<span class="me1">messageType</span><span class="br0">(</span>MessageType.<span class="me1">RAW</span><span class="br0">)</span>                     .<span class="me1">message</span><span class="br0">(</span>SdkBytes.<span class="me1">fromByteArray</span><span class="br0">(</span>outputStream.<span class="me1">toByteArray</span><span class="br0">(</span><span class="br0">)</span><span class="br0">)</span><span class="br0">)</span>                     .<span class="me1">build</span><span class="br0">(</span><span class="br0">)</span><span class="sy0">;</span>             SignResponse signResponse <span class="sy0">=</span> kmsClient.<span class="me1">sign</span><span class="br0">(</span>signRequest<span class="br0">)</span><span class="sy0">;</span>             SdkBytes signatureSdkBytes <span class="sy0">=</span> signResponse.<span class="me1">signature</span><span class="br0">(</span><span class="br0">)</span><span class="sy0">;</span>             <span class="kw1">return</span> signatureSdkBytes.<span class="me1">asByteArray</span><span class="br0">(</span><span class="br0">)</span><span class="sy0">;</span>         <span class="br0">}</span> <span class="kw1">finally</span> <span class="br0">{</span>             outputStream.<span class="me1">reset</span><span class="br0">(</span><span class="br0">)</span><span class="sy0">;</span>         <span class="br0">}</span>     <span class="br0">}</span>       @Override     <span class="kw1">public</span> <span class="kw3">OutputStream</span> getOutputStream<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>         <span class="kw1">return</span> outputStream<span class="sy0">;</span>     <span class="br0">}</span>       @Override     <span class="kw1">public</span> AlgorithmIdentifier getAlgorithmIdentifier<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>         <span class="kw1">return</span> signatureAlgorithm<span class="sy0">;</span>     <span class="br0">}</span>       <span class="kw1">final</span> <span class="kw1">static</span> Map<span class="sy0">&lt;</span>SigningAlgorithmSpec, String<span class="sy0">&gt;</span> signingAlgorithmNameBySpec<span class="sy0">;</span>       <span class="kw1">static</span> <span class="br0">{</span>         signingAlgorithmNameBySpec <span class="sy0">=</span> <span class="kw1">new</span> HashMap<span class="sy0">&lt;&gt;</span><span class="br0">(</span><span class="br0">)</span><span class="sy0">;</span>         signingAlgorithmNameBySpec.<span class="me1">put</span><span class="br0">(</span>SigningAlgorithmSpec.<span class="me1">ECDSA_SHA_256</span>, <span class="st0">"SHA256withECDSA"</span><span class="br0">)</span><span class="sy0">;</span>         signingAlgorithmNameBySpec.<span class="me1">put</span><span class="br0">(</span>SigningAlgorithmSpec.<span class="me1">ECDSA_SHA_384</span>, <span class="st0">"SHA384withECDSA"</span><span class="br0">)</span><span class="sy0">;</span>         signingAlgorithmNameBySpec.<span class="me1">put</span><span class="br0">(</span>SigningAlgorithmSpec.<span class="me1">ECDSA_SHA_512</span>, <span class="st0">"SHA512withECDSA"</span><span class="br0">)</span><span class="sy0">;</span>         signingAlgorithmNameBySpec.<span class="me1">put</span><span class="br0">(</span>SigningAlgorithmSpec.<span class="me1">RSASSA_PKCS1_V1_5_SHA_256</span>, <span class="st0">"SHA256withRSA"</span><span class="br0">)</span><span class="sy0">;</span>         signingAlgorithmNameBySpec.<span class="me1">put</span><span class="br0">(</span>SigningAlgorithmSpec.<span class="me1">RSASSA_PKCS1_V1_5_SHA_384</span>, <span class="st0">"SHA384withRSA"</span><span class="br0">)</span><span class="sy0">;</span>         signingAlgorithmNameBySpec.<span class="me1">put</span><span class="br0">(</span>SigningAlgorithmSpec.<span class="me1">RSASSA_PKCS1_V1_5_SHA_512</span>, <span class="st0">"SHA512withRSA"</span><span class="br0">)</span><span class="sy0">;</span>         signingAlgorithmNameBySpec.<span class="me1">put</span><span class="br0">(</span>SigningAlgorithmSpec.<span class="me1">RSASSA_PSS_SHA_256</span>, <span class="st0">"SHA256withRSAandMGF1"</span><span class="br0">)</span><span class="sy0">;</span>         signingAlgorithmNameBySpec.<span class="me1">put</span><span class="br0">(</span>SigningAlgorithmSpec.<span class="me1">RSASSA_PSS_SHA_384</span>, <span class="st0">"SHA384withRSAandMGF1"</span><span class="br0">)</span><span class="sy0">;</span>         signingAlgorithmNameBySpec.<span class="me1">put</span><span class="br0">(</span>SigningAlgorithmSpec.<span class="me1">RSASSA_PSS_SHA_512</span>, <span class="st0">"SHA512withRSAandMGF1"</span><span class="br0">)</span><span class="sy0">;</span>     <span class="br0">}</span> <span class="br0">}</span></pre></td></tr></tbody></table></div></code></pre> <p> </p> </div> </div> </div> </section> <section class="brick jdoodle__brick"> <div class="brick__inner"> <div class="paragraph paragraph--jdoodle paragraph--jdoodle--default"> <h2></h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <p><em><a href="https://kb.itextpdf.com/itext/using-itext-7-and-aws-kms-to-digitally-sign-a-pd-1">AwsContentSigner</a></em></p> <p>For production purposes however, you'll usually want to use a certificate signed by a trusted Certificate Authority (CA). Similar to the example above you can create and sign a <em>certificate request</em> for your AWS KMS public key, send it to your CA of choice, and get back the certificate to use from them.</p> <p>There’s no .NET equivalent of <code>AwsKmsContentSigner.java</code> since our .NET version of <code>CertificateUtils</code> doesn’t use BouncyCastle for certificate generation but instead uses the .NET <code>X509SignatureGenerator</code>, thus no BouncyCastle <code>ContentSigner</code> implementation is required.</p> <h3 id="UsingiText7andAWSKMStodigitallysignaPDFdocument-SigningaPDFusinganAWSKMSKeyPair">Signing a PDF using an AWS KMS Key Pair</h3> <p>To sign a PDF with iText you need an implementation of the iText <code>IExternalSignature</code> or <code>IExternalSignatureContainer</code> interface. Here we use the former:</p> </div> &lt;div data-pym-src=&quot;https://itext.jdoodle.com/embed/itext&quot; data-java-libs=&quot;com.itextpdf:itext7-core:latest.release,org.slf4j:slf4j-api:1.7.28,org.slf4j:slf4j-simple:1.7.28,com.itextpdf:pdfocr-tesseract4:latest.release&quot; data-csharp-libs=&quot;itext7:latest.release&quot; data-show-upload=&quot;false&quot; data-hide-execute=&quot;true&quot; data-max-uploads=&quot;3&quot; data-max-file-size=&quot;5000000&quot;&gt; &lt;div data-script-type=&quot;java&quot;&gt;&lt;script&gt;public class AwsKmsSignature implements IExternalSignature { public AwsKmsSignature(String keyId) { this(keyId, a -&gt; a != null &amp;&amp; a.size() &gt; 0 ? a.get(0) : null); } public AwsKmsSignature(String keyId, Function&lt;List&lt;SigningAlgorithmSpec&gt;, SigningAlgorithmSpec&gt; selector) { this.keyId = keyId; try ( KmsClient kmsClient = KmsClient.create() ) { GetPublicKeyRequest getPublicKeyRequest = GetPublicKeyRequest.builder() .keyId(keyId) .build(); GetPublicKeyResponse getPublicKeyResponse = kmsClient.getPublicKey(getPublicKeyRequest); signingAlgorithmSpec = selector.apply(getPublicKeyResponse.signingAlgorithms()); switch(signingAlgorithmSpec) { case ECDSA_SHA_256: case ECDSA_SHA_384: case ECDSA_SHA_512: case RSASSA_PKCS1_V1_5_SHA_256: case RSASSA_PKCS1_V1_5_SHA_384: case RSASSA_PKCS1_V1_5_SHA_512: case RSASSA_PSS_SHA_256: case RSASSA_PSS_SHA_384: case RSASSA_PSS_SHA_512: break; default: throw new IllegalArgumentException(String.format(&quot;Unknown signing algorithm: %s&quot;, signingAlgorithmSpec)); } } } @Override public String getDigestAlgorithmName() { switch(signingAlgorithmSpec) { case ECDSA_SHA_256: case RSASSA_PKCS1_V1_5_SHA_256: case RSASSA_PSS_SHA_256: return &quot;SHA-256&quot;; case ECDSA_SHA_384: case RSASSA_PKCS1_V1_5_SHA_384: case RSASSA_PSS_SHA_384: return &quot;SHA-384&quot;; case ECDSA_SHA_512: case RSASSA_PKCS1_V1_5_SHA_512: case RSASSA_PSS_SHA_512: return &quot;SHA-512&quot;; default: return null; } } @Override public String getSignatureAlgorithmName() { switch(signingAlgorithmSpec) { case ECDSA_SHA_256: case ECDSA_SHA_384: case ECDSA_SHA_512: return &quot;ECDSA&quot;; case RSASSA_PKCS1_V1_5_SHA_256: case RSASSA_PKCS1_V1_5_SHA_384: case RSASSA_PKCS1_V1_5_SHA_512: return &quot;RSA&quot;; case RSASSA_PSS_SHA_256: case RSASSA_PSS_SHA_384: case RSASSA_PSS_SHA_512: return &quot;RSASSA-PSS&quot;; default: return null; } } @Override public ISignatureMechanismParams getSignatureMechanismParameters() { switch (signingAlgorithmSpec) { case RSASSA_PSS_SHA_256: return RSASSAPSSMechanismParams.createForDigestAlgorithm(&quot;SHA-256&quot;); case RSASSA_PSS_SHA_384: return RSASSAPSSMechanismParams.createForDigestAlgorithm(&quot;SHA-384&quot;); case RSASSA_PSS_SHA_512: return RSASSAPSSMechanismParams.createForDigestAlgorithm(&quot;SHA-512&quot;); case ECDSA_SHA_256: case ECDSA_SHA_384: case ECDSA_SHA_512: case RSASSA_PKCS1_V1_5_SHA_256: case RSASSA_PKCS1_V1_5_SHA_384: case RSASSA_PKCS1_V1_5_SHA_512: default: return null; } } @Override public byte[] sign(byte[] message) throws GeneralSecurityException { try ( KmsClient kmsClient = KmsClient.create() ) { SignRequest signRequest = SignRequest.builder() .signingAlgorithm(signingAlgorithmSpec) .keyId(keyId) .messageType(MessageType.RAW) .message(SdkBytes.fromByteArray(message)) .build(); SignResponse signResponse = kmsClient.sign(signRequest); return signResponse.signature().asByteArray(); } } final String keyId; final SigningAlgorithmSpec signingAlgorithmSpec; }&lt;/script&gt;&lt;/div&gt; &lt;div data-script-type=&quot;csharp&quot;&gt;&lt;script&gt;public class AwsKmsSignature : IExternalSignature { public AwsKmsSignature(string keyId, Func&lt;List&lt;string&gt;, string&gt; selector) { this.keyId = keyId; using (var kmsClient = new AmazonKeyManagementServiceClient()) { GetPublicKeyRequest getPublicKeyRequest = new GetPublicKeyRequest() { KeyId = keyId }; GetPublicKeyResponse getPublicKeyResponse = kmsClient.GetPublicKeyAsync(getPublicKeyRequest).Result; List&lt;string&gt; signingAlgorithms = getPublicKeyResponse.SigningAlgorithms; signingAlgorithm = selector.Invoke(signingAlgorithms); switch(signingAlgorithm) { case &quot;ECDSA_SHA_256&quot;: case &quot;ECDSA_SHA_384&quot;: case &quot;ECDSA_SHA_512&quot;: case &quot;RSASSA_PKCS1_V1_5_SHA_256&quot;: case &quot;RSASSA_PKCS1_V1_5_SHA_384&quot;: case &quot;RSASSA_PKCS1_V1_5_SHA_512&quot;: case &quot;RSASSA_PSS_SHA_256&quot;: case &quot;RSASSA_PSS_SHA_384&quot;: case &quot;RSASSA_PSS_SHA_512&quot;: break; default: throw new ArgumentException(String.Format(&quot;Unknown signing algorithm: {0}&quot;, signingAlgorithm)); } } } public string GetSignatureAlgorithmName() { switch (signingAlgorithm) { case &quot;ECDSA_SHA_256&quot;: case &quot;ECDSA_SHA_384&quot;: case &quot;ECDSA_SHA_512&quot;: return &quot;ECDSA&quot;; case &quot;RSASSA_PKCS1_V1_5_SHA_256&quot;: case &quot;RSASSA_PKCS1_V1_5_SHA_384&quot;: case &quot;RSASSA_PKCS1_V1_5_SHA_512&quot;: return &quot;RSA&quot;; case &quot;RSASSA_PSS_SHA_256&quot;: case &quot;RSASSA_PSS_SHA_384&quot;: case &quot;RSASSA_PSS_SHA_512&quot;: return &quot;RSASSA-PSS&quot;; default: return null; } } public string GetDigestAlgorithmName() { switch (signingAlgorithm) { case &quot;ECDSA_SHA_256&quot;: case &quot;RSASSA_PKCS1_V1_5_SHA_256&quot;: case &quot;RSASSA_PSS_SHA_256&quot;: return &quot;SHA-256&quot;; case &quot;ECDSA_SHA_384&quot;: case &quot;RSASSA_PKCS1_V1_5_SHA_384&quot;: case &quot;RSASSA_PSS_SHA_384&quot;: return &quot;SHA-384&quot;; case &quot;ECDSA_SHA_512&quot;: case &quot;RSASSA_PKCS1_V1_5_SHA_512&quot;: case &quot;RSASSA_PSS_SHA_512&quot;: return &quot;SHA-512&quot;; default: return null; } } public ISignatureMechanismParams GetSignatureMechanismParameters() { switch (signingAlgorithm) { case &quot;RSASSA_PSS_SHA_256&quot;: return RSASSAPSSMechanismParams.CreateForDigestAlgorithm(&quot;SHA-256&quot;); case &quot;RSASSA_PSS_SHA_384&quot;: return RSASSAPSSMechanismParams.CreateForDigestAlgorithm(&quot;SHA-384&quot;); case &quot;RSASSA_PSS_SHA_512&quot;: return RSASSAPSSMechanismParams.CreateForDigestAlgorithm(&quot;SHA-512&quot;); case &quot;ECDSA_SHA_256&quot;: case &quot;ECDSA_SHA_384&quot;: case &quot;ECDSA_SHA_512&quot;: case &quot;RSASSA_PKCS1_V1_5_SHA_256&quot;: case &quot;RSASSA_PKCS1_V1_5_SHA_384&quot;: case &quot;RSASSA_PKCS1_V1_5_SHA_512&quot;: default: return null; } } public byte[] Sign(byte[] message) { using (var kmsClient = new AmazonKeyManagementServiceClient()) { SignRequest signRequest = new SignRequest() { SigningAlgorithm = signingAlgorithm, KeyId=keyId, MessageType=MessageType.RAW, Message=new MemoryStream(message) }; SignResponse signResponse = kmsClient.SignAsync(signRequest).Result; return signResponse.Signature.ToArray(); } } string keyId; string signingAlgorithm; } }&lt;/script&gt;&lt;/div&gt; &lt;/div&gt; </div> </div> </section> <section class="brick jdoodle__brick"> <div class="brick__inner"> <div class="paragraph paragraph--jdoodle paragraph--jdoodle--default"> <h2></h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <p><a href="https://kb.itextpdf.com/itext/using-itext-7-and-aws-kms-to-digitally-sign-a-pd-2"><em>AwsKmsSignature</em></a></p> <p>In the constructor we select a signing algorithm available for the key in question. The single argument constructor simply takes the first algorithm from the available ones, the double argument constructor allows you to select a specific one.</p> <p><code>getDigestAlgorithmName</code> returns the name of the respective part of the signature algorithm, <code>getSignatureMechanismParameters</code> returns additional parameters where required, and <code>sign</code> simply creates a signature.</p> <p>For .NET, the <code>AwsKmsSignature</code> class could be ported from Java with very little changes required.</p> <h3 id="UsingiText7andAWSKMStodigitallysignaPDFdocument-PuttingItIntoAction">Putting It Into Action</h3> <p>Assuming your AWS KMS signing key pair has the alias <code>SigningExamples-ECC_NIST_P256</code> <strong><em>and is indeed an ECC_NIST_P256 key pair</em></strong> you can use the code above to sign a PDF like this:</p> </div> &lt;div data-pym-src=&quot;https://itext.jdoodle.com/embed/itext&quot; data-java-libs=&quot;com.itextpdf:itext7-core:latest.release,org.slf4j:slf4j-api:1.7.28,org.slf4j:slf4j-simple:1.7.28,com.itextpdf:pdfocr-tesseract4:latest.release&quot; data-csharp-libs=&quot;itext7:latest.release&quot; data-show-upload=&quot;false&quot; data-hide-execute=&quot;true&quot; data-max-uploads=&quot;3&quot; data-max-file-size=&quot;5000000&quot;&gt; &lt;div data-script-type=&quot;java&quot;&gt;&lt;script&gt;BouncyCastleProvider provider = new BouncyCastleProvider(); Security.addProvider(provider); String keyId = &quot;alias/SigningExamples-ECC_NIST_P256&quot;; AwsKmsSignature signature = new AwsKmsSignature(keyId); Certificate certificate = CertificateUtils.generateSelfSignedCertificate(keyId, &quot;CN=AWS KMS PDF Signing Test,OU=mkl tests,O=mkl&quot;); try ( PdfReader pdfReader = new PdfReader(PDF_TO_SIGN); OutputStream result = new FileOutputStream(SIGNED_PDF)) { PdfSigner pdfSigner = new PdfSigner(pdfReader, result, new StampingProperties().useAppendMode()); IExternalDigest externalDigest = new BouncyCastleDigest(); pdfSigner.signDetached(externalDigest , signature, new Certificate[] {certificate}, null, null, null, 0, CryptoStandard.CMS); }&lt;/script&gt;&lt;/div&gt; &lt;div data-script-type=&quot;csharp&quot;&gt;&lt;script&gt;string keyId = &quot;alias/SigningExamples-ECC_NIST_P256&quot;; Func&lt;System.Collections.Generic.List&lt;string&gt;, string&gt; selector = list =&gt; list.Find(name =&gt; name.StartsWith(&quot;ECDSA_SHA_256&quot;)); AwsKmsSignature signature = new AwsKmsSignature(keyId, selector); System.Security.Cryptography.X509Certificates.X509Certificate2 certificate2 = CertificateUtils.generateSelfSignedCertificate( keyId, &quot;CN=AWS KMS PDF Signing Test ECDSA,OU=mkl tests,O=mkl&quot;, selector ); X509Certificate certificate = new X509Certificate(X509CertificateStructure.GetInstance(certificate2.RawData)); using (PdfReader pdfReader = new PdfReader(PDF_TO_SIGN)) using (FileStream result = File.Create(SIGNED_PDF)) { PdfSigner pdfSigner = new PdfSigner(pdfReader, result, new StampingProperties().UseAppendMode()); pdfSigner.SignDetached(signature, new X509Certificate[] { certificate }, null, null, null, 0, CryptoStandard.CMS); }&lt;/script&gt;&lt;/div&gt; &lt;/div&gt; </div> </div> </section> <section class="brick jdoodle__brick"> <div class="brick__inner"> <div class="paragraph paragraph--jdoodle paragraph--jdoodle--default"> <h2></h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <p><em><a href="https://kb.itextpdf.com/itext/using-itext-7-and-aws-kms-to-digitally-sign-a-pd-3">TestSignSimple</a> test </em><code>testSignSimpleEcdsa</code></p> <h3 id="UsingiText7andAWSKMStodigitallysignaPDFdocument-SigningaPDFUsinganAWSKMSKeyPair:Redux">Signing a PDF Using an AWS KMS Key Pair: Redux</h3> <p>Above we used an implementation of <code>IExternalSignature</code> for signing. While that is the easiest way, it has some drawbacks: The class <code>PdfPKCS7</code> used in this case is not very flexible, e.g. it doesn't allow to to add custom attributes to the signature.</p> <p>To not be subject to these limitations, we here use an implementation of <code>IExternalSignatureContainer</code> instead in which we build the complete CMS signature container ourselves using only BouncyCastle functionality.</p> <p>For .NET, while the <code>AwsKmsSignatureContainer</code> class uses BouncyCastle to build the CMS signature container to embed just like in the Java version, there are certain differences in the .NET BouncyCastle API. In particular one does not use an instance of <code>ContentSigner</code> for the actual signing but an instance of <code>ISignatureFactory</code>; that interface represents a factory of <code>IStreamCalculator</code> instances which in their function are equivalent to the <code>ContentSigner</code> in Java. The implementations of these interfaces are <code>AwsKmsSignatureFactory</code> and <code>AwsKmsStreamCalculator</code> in the .NET example.</p> </div> &lt;div data-pym-src=&quot;https://itext.jdoodle.com/embed/itext&quot; data-java-libs=&quot;com.itextpdf:itext7-core:latest.release,org.slf4j:slf4j-api:1.7.28,org.slf4j:slf4j-simple:1.7.28,com.itextpdf:pdfocr-tesseract4:latest.release&quot; data-csharp-libs=&quot;itext7:latest.release&quot; data-show-upload=&quot;false&quot; data-hide-execute=&quot;true&quot; data-max-uploads=&quot;3&quot; data-max-file-size=&quot;5000000&quot;&gt; &lt;div data-script-type=&quot;java&quot;&gt;&lt;script&gt;public class AwsKmsSignatureContainer implements IExternalSignatureContainer { public AwsKmsSignatureContainer(X509Certificate x509Certificate, String keyId) { this(x509Certificate, keyId, a -&gt; a != null &amp;&amp; a.size() &gt; 0 ? a.get(0) : null); } public AwsKmsSignatureContainer(X509Certificate x509Certificate, String keyId, Function&lt;List&lt;SigningAlgorithmSpec&gt;, SigningAlgorithmSpec&gt; selector) { this.x509Certificate = x509Certificate; this.keyId = keyId; try ( KmsClient kmsClient = KmsClient.create() ) { GetPublicKeyRequest getPublicKeyRequest = GetPublicKeyRequest.builder() .keyId(keyId) .build(); GetPublicKeyResponse getPublicKeyResponse = kmsClient.getPublicKey(getPublicKeyRequest); signingAlgorithmSpec = selector.apply(getPublicKeyResponse.signingAlgorithms()); if (signingAlgorithmSpec == null) throw new IllegalArgumentException(&quot;KMS key has no signing algorithms&quot;); contentSigner = new AwsKmsContentSigner(keyId, signingAlgorithmSpec); } } @Override public byte[] sign(InputStream data) throws GeneralSecurityException { try { CMSTypedData msg = new CMSTypedDataInputStream(data); X509CertificateHolder signCert = new X509CertificateHolder(x509Certificate.getEncoded()); CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); gen.addSignerInfoGenerator( new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(&quot;BC&quot;).build()) .build(contentSigner, signCert)); gen.addCertificates(new JcaCertStore(Collections.singleton(signCert))); CMSSignedData sigData = gen.generate(msg, false); return sigData.getEncoded(); } catch (IOException | OperatorCreationException | CMSException e) { throw new GeneralSecurityException(e); } } @Override public void modifySigningDictionary(PdfDictionary signDic) { signDic.put(PdfName.Filter, new PdfName(&quot;MKLx_AWS_KMS_SIGNER&quot;)); signDic.put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached); } final X509Certificate x509Certificate; final String keyId; final SigningAlgorithmSpec signingAlgorithmSpec; final ContentSigner contentSigner; class CMSTypedDataInputStream implements CMSTypedData { InputStream in; public CMSTypedDataInputStream(InputStream is) { in = is; } @Override public ASN1ObjectIdentifier getContentType() { return PKCSObjectIdentifiers.data; } @Override public Object getContent() { return in; } @Override public void write(OutputStream out) throws IOException, CMSException { byte[] buffer = new byte[8 * 1024]; int read; while ((read = in.read(buffer)) != -1) { out.write(buffer, 0, read); } in.close(); } } }&lt;/script&gt;&lt;/div&gt; &lt;div data-script-type=&quot;csharp&quot;&gt;&lt;script&gt; public class AwsKmsSignatureContainer : IExternalSignatureContainer { public AwsKmsSignatureContainer(X509Certificate x509Certificate, string keyId, Func&lt;List&lt;string&gt;, string&gt; selector) { this.x509Certificate = x509Certificate; this.keyId = keyId; using (var kmsClient = new AmazonKeyManagementServiceClient()) { GetPublicKeyRequest getPublicKeyRequest = new GetPublicKeyRequest() { KeyId = keyId }; GetPublicKeyResponse getPublicKeyResponse = kmsClient.GetPublicKeyAsync(getPublicKeyRequest).Result; List&lt;string&gt; signingAlgorithms = getPublicKeyResponse.SigningAlgorithms; this.signingAlgorithm = selector.Invoke(signingAlgorithms); if (signingAlgorithm == null) throw new ArgumentException(&quot;KMS key has no signing algorithms&quot;, nameof(keyId)); signatureFactory = new AwsKmsSignatureFactory(keyId, signingAlgorithm); } } public void ModifySigningDictionary(PdfDictionary signDic) { signDic.Put(PdfName.Filter, new PdfName(&quot;MKLx_AWS_KMS_SIGNER&quot;)); signDic.Put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached); } public byte[] Sign(Stream data) { CmsProcessable msg = new CmsProcessableInputStream(data); CmsSignedDataGenerator gen = new CmsSignedDataGenerator(); SignerInfoGenerator signerInfoGenerator = new SignerInfoGeneratorBuilder() .WithSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator()) .Build(signatureFactory, x509Certificate); gen.AddSignerInfoGenerator(signerInfoGenerator); IStore&lt;X509Certificate&gt; store =CollectionUtilities.CreateStore(new List&lt;X509Certificate&gt; { x509Certificate }); gen.AddCertificates(store); CmsSignedData sigData = gen.Generate(msg, false); return sigData.GetEncoded(); } X509Certificate x509Certificate; String keyId; string signingAlgorithm; ISignatureFactory signatureFactory; } class AwsKmsSignatureFactory : ISignatureFactory { private string keyId; private string signingAlgorithm; private AlgorithmIdentifier signatureAlgorithm; public AwsKmsSignatureFactory(string keyId, string signingAlgorithm) { this.keyId = keyId; this.signingAlgorithm = signingAlgorithm; string signatureAlgorithmName = signingAlgorithmNameBySpec[signingAlgorithm]; if (signatureAlgorithmName == null) throw new ArgumentException(&quot;Unknown signature algorithm &quot; + signingAlgorithm, nameof(signingAlgorithm)); // Special treatment because of issue https://github.com/bcgit/bc-csharp/issues/250 switch (signatureAlgorithmName.ToUpperInvariant()) { case &quot;SHA256WITHECDSA&quot;: this.signatureAlgorithm = new AlgorithmIdentifier(X9ObjectIdentifiers.ECDsaWithSha256); break; case &quot;SHA512WITHECDSA&quot;: this.signatureAlgorithm = new AlgorithmIdentifier(X9ObjectIdentifiers.ECDsaWithSha512); break; default: this.signatureAlgorithm = new DefaultSignatureAlgorithmIdentifierFinder().Find(signatureAlgorithmName); break; } } public object AlgorithmDetails =&gt; signatureAlgorithm; public IStreamCalculator&lt;IBlockResult&gt; CreateCalculator() { return new AwsKmsStreamCalculator(keyId, signingAlgorithm); } static Dictionary&lt;string, string&gt; signingAlgorithmNameBySpec = new Dictionary&lt;string, string&gt;() { { &quot;ECDSA_SHA_256&quot;, &quot;SHA256withECDSA&quot; }, { &quot;ECDSA_SHA_384&quot;, &quot;SHA384withECDSA&quot; }, { &quot;ECDSA_SHA_512&quot;, &quot;SHA512withECDSA&quot; }, { &quot;RSASSA_PKCS1_V1_5_SHA_256&quot;, &quot;SHA256withRSA&quot; }, { &quot;RSASSA_PKCS1_V1_5_SHA_384&quot;, &quot;SHA384withRSA&quot; }, { &quot;RSASSA_PKCS1_V1_5_SHA_512&quot;, &quot;SHA512withRSA&quot; }, { &quot;RSASSA_PSS_SHA_256&quot;, &quot;SHA256withRSAandMGF1&quot;}, { &quot;RSASSA_PSS_SHA_384&quot;, &quot;SHA384withRSAandMGF1&quot;}, { &quot;RSASSA_PSS_SHA_512&quot;, &quot;SHA512withRSAandMGF1&quot;} }; } class AwsKmsStreamCalculator : IStreamCalculator&lt;IBlockResult&gt; { private string keyId; private string signingAlgorithm; private MemoryStream stream = new MemoryStream(); public AwsKmsStreamCalculator(string keyId, string signingAlgorithm) { this.keyId = keyId; this.signingAlgorithm = signingAlgorithm; } public Stream Stream =&gt; stream; public IBlockResult GetResult() { try { using (var kmsClient = new AmazonKeyManagementServiceClient()) { SignRequest signRequest = new SignRequest() { SigningAlgorithm = signingAlgorithm, KeyId = keyId, MessageType = MessageType.RAW, Message = new MemoryStream(stream.ToArray()) }; SignResponse signResponse = kmsClient.SignAsync(signRequest).Result; return new SimpleBlockResult(signResponse.Signature.ToArray()); } } finally { stream = new MemoryStream(); } } }&lt;/script&gt;&lt;/div&gt; &lt;/div&gt; </div> </div> </section> <section class="brick jdoodle__brick"> <div class="brick__inner"> <div class="paragraph paragraph--jdoodle paragraph--jdoodle--default"> <h2></h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <p id="UsingiText7andAWSKMStodigitallysignaPDFdocument-PuttingItIntoAction:Redux"><a href="https://kb.itextpdf.com/itext/using-itext-7-and-aws-kms-to-digitally-sign-a-pd-4"><em>A</em></a><a href="https://kb.itextpdf.com/itext/using-itext-7-and-aws-kms-to-digitally-sign-a-pd-4"><em>wsKmsSignatureContainer</em></a></p> <h3>Putting It Into Action: Redux</h3> <p>Assuming you have an AWS KMS signing RSA_2048 key pair which has the alias <code>SigningExamples-RSA_2048</code> you can use the code above like this to sign a PDF using RSASSA-PSS:</p> </div> &lt;div data-pym-src=&quot;https://itext.jdoodle.com/embed/itext&quot; data-java-libs=&quot;com.itextpdf:itext7-core:latest.release,org.slf4j:slf4j-api:1.7.28,org.slf4j:slf4j-simple:1.7.28,com.itextpdf:pdfocr-tesseract4:latest.release&quot; data-csharp-libs=&quot;itext7:latest.release&quot; data-show-upload=&quot;false&quot; data-hide-execute=&quot;true&quot; data-max-uploads=&quot;3&quot; data-max-file-size=&quot;5000000&quot;&gt; &lt;div data-script-type=&quot;java&quot;&gt;&lt;script&gt;BouncyCastleProvider provider = new BouncyCastleProvider(); Security.addProvider(provider); String keyId = &quot;alias/SigningExamples-RSA_2048&quot;; X509Certificate certificate = CertificateUtils.generateSelfSignedCertificate(keyId, &quot;CN=AWS KMS PDF Signing Test,OU=mkl tests,O=mkl&quot;); AwsKmsSignatureContainer signatureContainer = new AwsKmsSignatureContainer(certificate, keyId, TestSignSimple::selectRsaSsaPss); try ( PdfReader pdfReader = new PdfReader(PDF_TO_SIGN); OutputStream result = new FileOutputStream(SIGNED_PDF)) { PdfSigner pdfSigner = new PdfSigner(pdfReader, result, new StampingProperties().useAppendMode()); pdfSigner.signExternalContainer(signatureContainer, 8192); }&lt;/script&gt;&lt;/div&gt; &lt;div data-script-type=&quot;csharp&quot;&gt;&lt;script&gt;string keyId = &quot;alias/SigningExamples-RSA_2048&quot;; Func&lt;System.Collections.Generic.List&lt;string&gt;, string&gt; selector = list =&gt; list.Find(name =&gt; name.StartsWith(&quot;RSASSA_PSS&quot;)); System.Security.Cryptography.X509Certificates.X509Certificate2 certificate2 = CertificateUtils.generateSelfSignedCertificate( keyId, &quot;CN=AWS KMS PDF Signing Test RSAwithMGF1,OU=mkl tests,O=mkl&quot;, selector ); X509Certificate certificate = new X509Certificate(X509CertificateStructure.GetInstance(certificate2.RawData)); AwsKmsSignatureContainer signature = new AwsKmsSignatureContainer(certificate, keyId, selector); using (PdfReader pdfReader = new PdfReader(PDF_TO_SIGN)) using (FileStream result = File.Create(SIGNED_PDF)) { PdfSigner pdfSigner = new PdfSigner(pdfReader, result, new StampingProperties().UseAppendMode()); pdfSigner.SignExternalContainer(signature, 8192); }&lt;/script&gt;&lt;/div&gt; &lt;/div&gt; </div> </div> </section> <section class="brick jdoodle__brick"> <div class="brick__inner"> <div class="paragraph paragraph--jdoodle paragraph--jdoodle--default"> <h2></h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <p><em><a href="https://kb.itextpdf.com/itext/using-itext-7-and-aws-kms-to-digitally-sign-a-pd-3">TestSignSimple</a> test <code>testSignSimpleRsaSsaPssExternal</code></em></p> <p>with this selector function for Java:</p> <pre> <code class="language-java"><div class="geshifilter"><table class="java geshifilter-java"><tbody><tr class="li1"><td class="ln"><pre class="de1">1 2 3 4 5 6 </pre></td><td class="de1"><pre class="de1"><span class="kw1">static</span> SigningAlgorithmSpec selectRsaSsaPss <span class="br0">(</span>List<span class="sy0">&lt;</span>SigningAlgorithmSpec<span class="sy0">&gt;</span> specs<span class="br0">)</span> <span class="br0">{</span>     <span class="kw1">if</span> <span class="br0">(</span>specs <span class="sy0">!=</span> <span class="kw2">null</span><span class="br0">)</span>         <span class="kw1">return</span> specs.<span class="me1">stream</span><span class="br0">(</span><span class="br0">)</span>.<span class="me1">filter</span><span class="br0">(</span>spec <span class="sy0">-&gt;</span> spec.<span class="me1">toString</span><span class="br0">(</span><span class="br0">)</span>.<span class="me1">startsWith</span><span class="br0">(</span><span class="st0">"RSASSA_PSS"</span><span class="br0">)</span><span class="br0">)</span>.<span class="me1">findFirst</span><span class="br0">(</span><span class="br0">)</span>.<span class="me1">orElse</span><span class="br0">(</span><span class="kw2">null</span><span class="br0">)</span><span class="sy0">;</span>     <span class="kw1">else</span>         <span class="kw1">return</span> <span class="kw2">null</span><span class="sy0">;</span> <span class="br0">}</span></pre></td></tr></tbody></table></div></code></pre> <p> </p> </div> </div> </div> </section> <section class="brick jdoodle__brick"> <div class="brick__inner"> <div class="paragraph paragraph--jdoodle paragraph--jdoodle--default"> <h2></h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <p>For .NET, since the C# AWS KMS API works with String representations of the algorithm the corresponding expression on the C# side is:</p> <pre> <code class="language-java"><div class="geshifilter"><table class="java geshifilter-java"><tbody><tr class="li1"><td class="ln"><pre class="de1">1 </pre></td><td class="de1"><pre class="de1">Func<span class="sy0">&lt;</span><span class="kw3">System</span>.<span class="kw3">Collections</span>.<span class="me1">Generic</span>.<span class="me1">List</span><span class="sy0">&lt;</span>string<span class="sy0">&gt;</span>, string<span class="sy0">&gt;</span> selector <span class="sy0">=</span> list <span class="sy0">=&gt;</span> list.<span class="me1">Find</span><span class="br0">(</span>name <span class="sy0">=&gt;</span> name.<span class="me1">StartsWith</span><span class="br0">(</span><span class="st0">"RSASSA_PSS"</span><span class="br0">)</span><span class="br0">)</span><span class="sy0">;</span></pre></td></tr></tbody></table></div></code></pre> <p> </p> </div> </div> </div> </section> <div class="paragraph paragraph--text-area paragraph--text-area--default"> <h2></h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <h3 id="UsingiText7andAWSKMStodigitallysignaPDFdocument-FinalThoughtsandMass-SigningConsiderations">Final Thoughts and Mass-Signing Considerations</h3> <p>If you plan to do mass-signing using AWS KMS, you should be aware of the request quotas established by AWS KMS for some of its operations:</p> <table align="left" border="1" cellpadding="2" cellspacing="2" class="custom"><thead><tr><td> <p><strong>Quota Name</strong></p> </td> <td> <p><strong>Default value (per second)</strong></p> </td> </tr></thead><tbody><tr><td> <p>Cryptographic operations (RSA) request rate</p> </td> <td> <p>500 (shared) for RSA CMKs</p> </td> </tr><tr><td> <p>Cryptographic operations (ECC) request rate</p> </td> <td> <p>300 (shared) for elliptic curve (ECC) CMKs</p> </td> </tr><tr><td> <p>GetPublicKey request rate</p> </td> <td> <p>5</p> </td> </tr></tbody></table><p><br /><em>(excerpt from "AWS Key Management Service Developer Guide" / "Quotas" / "Request Quotas" / </em><a href="https://docs.aws.amazon.com/kms/latest/developerguide/requests-per-second.html#rps-table" rel="nofollow"><em>"Request quotas for each AWS KMS API operation"</em></a><em> viewed 1/28/2021</em><em>)</em></p> <p>The <em>RSA</em> and <em>ECC cryptographic operations request rates</em> are probably not a problem. Or more to the point, if they are a problem, AWS KMS is most likely not the right signing product for your needs. You should instead look for actual HSMs, be they physical or as-a-service, e.g., <a href="https://aws.amazon.com/cloudhsm/" rel="nofollow">AWS CloudHSM</a>.</p> <p>The <em>GetPublicKey request rate</em> on the other hand may well be a problem: Both the <code>AwsKmsSignature</code> and <code>AwsKmsSignatureContainer</code> constructors respectively call that method. Naive mass-signing code based on them, would therefore be limited to 5 signatures per second.</p> <p>Depending on your use case there are different strategies to tackle this problem.</p> <p><strong>If very few instances of your signing code are running concurrently and they are using only a very few different keys</strong>, you can simply re-use your <code>AwsKmsSignature</code> and <code>AwsKmsSignatureContainer</code> objects, either creating them at start-up or on-demand, and then caching them.</p> <p><strong>Otherwise</strong>, you should refactor the use of the <code>GetPublicKey</code> method outside of the <code>AwsKmsSignature</code> and <code>AwsKmsSignatureContainer</code> constructors. It is used inside there only to determine which AWS KMS signing algorithm identifier to use when signing with the key in question. Obviously, you can instead store that identifier together with the key identifier, making that <code>GetPublicKey</code> call unnecessary.</p> </div> </div> <div class="paragraph paragraph--text-area paragraph--text-area--default"> <h2> <div id="Conclusion" class="anchor field field--name-field-section-heading field--type-string field__items"> Conclusion </div> </h2> <div class="wysiwyg-content clearfix text-formatted field field--name-field-text-area field--type-text-long field__items"> <p>We hope you have found this article and its code examples useful if you’ve run into issues when using the AWS KMS or equivalent services. Once again, we’d like to thank Michael Klink for taking the time to port his Java examples from the initial Stack Overflow question to .NET, and indeed for his many contributions to the iText community.</p> <p> </p> </div> </div> </div> <div class="field field--name-field-related-products field--type-entity-reference field__items"> <div class="field__label">Related products</div> <a href="/products/itext-core" hreflang="en">iText Core</a> <a href="/products/itext-suite" hreflang="en">iText Suite</a> <a href="/products/itext-community" hreflang="en">iText Community</a> </div> <div class="field field--name-field-main-image field--type-entity-reference field__items"> <div class="field__label">Main image</div> <a href="/resources/media/images/using-itext-and-aws-kms-teaser" hreflang="en">Using iText and AWS KMS teaser</a> </div> <div class="field field--name-field-promoted-to-home-page-text field--type-string field__items"> <div class="field__label">Promoted to home page text</div> Newest blog: Digitally sign a PDF using AWS KMS and iText </div> Fri, 29 Jan 2021 13:10:15 +0000 ian.morris 14831 at https://itextpdf.com