简体   繁体   中英

Digitally Signing a PDF in Modern Firefox/Chrome/Edge Browsers

I have been down several rabbit holes looking for an answer for this.

I have a web application, written in AngularJS, that currently requires IE11 and the Acrobat plugin to digitally sign a PDF form. However, the plugin is garbage on IE11 and is not supported on modern browsers (which I define as Firefox, Chrome, and Edge. Safari will not be supported by my company.)

Because the application is AngularJS (and NOT running on Node), I need a javascript solution to sign the PDF. Not only that, but signature certificates are held on a smart card, meaning that I need a crypto library that can access the certs through some sort of PKCS#11 interface. In Javascript. Opening the forms externally in Acrobat is currently not acceptable to the customer.

I've looked at multiple libraries without being able to figure out a straight answer:

  • PKI.js
  • pkcs11.js
  • hwcrypto.js
  • graphene.js

None of these packages provide enough information to me to know whether to research them further.

Can anyone out there provide me further information or direction?

Thanks, Jason

This answer aims at workarounds, rather than actual answers.

This is because there's no API in the browser at the moment you could use to obtain smart card certificate's private key and use it. This has been supposedly discussed in the Web Crypto API and as far as I remember - the consensus was this should not be supported for security reasons (which I stronly disagree!).

You, as hundreds or thousands other developers (us included), are out of luck.

First workaround involves a .NET ClickOnce desktop app that is deployed at the server and run from the server. The app gets the security context of the current user session in runtime arguments so that the session is shared between the browser and the app that runs beside the browser. In this sense, running this app independently (without the session in the browser) would cause authorization issues during the communication with the server.

The app uses server's APIs to first retrieve the document user is about to sign. Then then app uses the local certificate store with no restrictions (as it's a regular desktop app), encrypts the document and sends it back to the server.

Pros: ClickOnce apps can be invoked from within the browser.

Cons: this requires .NET runtime at the client.

Second workaround involves a Java desktop app that is installed independently at the client's machine. You provide install packages for selected OSes (say Windows, Linux, MacOS), the user downloads the install package and installs the app in their OS.

Then, when the browser is supposed to sign a document, you provide an instruction that tells the user to run the app in the background. The app, when run, exposes an HTTP listener on a localhost and fixed port with two services

  • a push service that accepts the document data to be signed
  • a pull service that exposes the signed document, when it's available

As you guess, it's the browser that does the request, the browser makes a request to the localhost:port and uploads the document data to the push service. The Java app switches from waiting for the document to signing the document state. The user is supposed to use the app - pick a cert from the store (no restrictions since is a regular Java desktop app) and sign the document. Your browser in the background pings the pull service of the app and when the data is ready, the browser downloads it. Then, the browser uploads the signed document to the actual server, using the actual authenticated session.

There's a potential security hole here, as any local app or any opened web page could ping the pull service and download the document (which of course you don't want). We are aware of two fixes to this.

First, you can have yet another service in the Java app that returns a one-time authentication token (a guid for example) that is meant to be read once and then supplied with every call to the pull service as an authentication token. If any other malicious app or webpage reads the token before your's app web page does, your page will get errors from the pull service (as the one-time token has been apparently stolen and was not available). The web page could signal a communication error here and warn the user of a potential security issue.

Second way to fix the hole involve an argument to the pull service call that is provided by the application server and put in the page's script as a value, a token signed by the server's certificate. Your Java app can have the public key of the server's certificate so that the Java app is able to verify the argument's signature. But no other app (and no other page) will be able to forge the token (as the token's signature's private key is only available at your server) and there's no easy way to steal the valid token from the page's body.

Pros: the Java app could possibly target multiple OSes Cons: this still requires the Java runtime at the client

Both workarounds are tested in production and both work well for years. I hope this gives you one possible direction your final solution could be based on.

@Json, you said "it wasn't what I wanted to hear, but I'll accept your answer." !!

I would suggest use of Browser Extensions for digitally signing pdf, file, or anything from modern browsers. Browser extension will plug between browser providing JavaScript to browser connecting to extension host (application) running locally and accessing CertStore.

Our company has one such extension published and is free which uses .NET framework 3.5 which is generally available on all Windows clients. It does not use PKCS#11 simply eliminating need to provide PKCS#11 drivers etc and just works transparently once installed successfully. It uses Windows CertStore.

As per your comment, you have more clients running firefox, but we have Edge and Firefox extensions in development.

Setup (of host application running behind the chrome browser on windows) may be downloaded from https://download.cnet.com/Signer-Digital-Chrome-Extension/3000-33362_4-78042540.html Installing this host and restarting Chrome will automatically add Signer.Digital Chrome Extension

The actual working of this extension is illustrated here

Javascript to call method from extension:

    //Calculate Sign for the Hash by Calling function from Extension SignerDigital
    SignerDigital.signPdfHash(hash, $("#CertThumbPrint").val(), "SHA-256")      //or "SHA256"
     .then(
            function (signDataResp) {
              //Send signDataResp to Server
        },
            function (errmsg) {
                //Send errmsg to server or display the result in browser.
              }
     );

If success, returns Base64 encoded pkcs7 signature - use any suitable library to inject sign to pdf If Failed, returns error msg starting with "SDHost Error:"

I hope this helps!

i´m looking for something like this since 2016. In Brazil we have Lacuna Software WEBPKI but its must expensive.

I found this yesterday: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/pkcs11

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM