|
| 1 | +var fs = require('fs'); |
| 2 | +var path = require('path'); |
| 3 | +var signpdf = require('@signpdf/signpdf').default; |
| 4 | +var plainAddPlaceholder = require('@signpdf/placeholder-plain').plainAddPlaceholder; |
| 5 | +var ExternalSigner = require('@signpdf/utils').ExternalSigner; |
| 6 | +var crypto = require('crypto'); |
| 7 | +var createCertificate = require('./utils').createCertificate; |
| 8 | + |
| 9 | +// ExternalSigner implementation using the WebCrypto API |
| 10 | +// Note that this is just an example implementation of the ExternalSigner abstract class. |
| 11 | +// WebCrypto signing can also be implemented more easily by subclassing the Signer abstract |
| 12 | +// class directly, as is done in the `webcrypto.js` example script. |
| 13 | +class CryptoSigner extends ExternalSigner { |
| 14 | + // 'SHA-256', 'SHA-384' or 'SHA-512' are supported by webcrypto |
| 15 | + supportedHashAlgorithms = ['SHA-256', 'SHA-384', 'SHA-512']; |
| 16 | + |
| 17 | + // 'RSASSA-PKCS1-v1_5', 'RSA-PSS' or 'ECDSA' are supported by webcrypto |
| 18 | + supportedSignAlgorithms = ['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'ECDSA']; |
| 19 | + |
| 20 | + constructor(signAlgorithm = 'ECDSA', hashAlgorithm = 'SHA-512') { |
| 21 | + super(); |
| 22 | + |
| 23 | + // Verify and set signature and hash algorithms |
| 24 | + if (!this.supportedSignAlgorithms.includes(signAlgorithm)) { |
| 25 | + throw new Error(`Signature algorithm ${signAlgorithm} is not supported by WebCrypto.`); |
| 26 | + } |
| 27 | + this.signAlgorithm = signAlgorithm; |
| 28 | + if (!this.supportedHashAlgorithms.includes(hashAlgorithm)) { |
| 29 | + throw new Error(`Hash algorithm ${hashAlgorithm} is not supported by WebCrypto.`); |
| 30 | + } |
| 31 | + this.hashAlgorithm = hashAlgorithm; |
| 32 | + |
| 33 | + // Salt lengths for RSA-PSS algorithm used by PKI.js |
| 34 | + // If you want to modify these, the crypto.getSignatureParameters |
| 35 | + // method needs to be overridden in the getCrypto function. |
| 36 | + this.saltLengths = { |
| 37 | + 'SHA-256': 32, |
| 38 | + 'SHA-384': 48, |
| 39 | + 'SHA-512': 64, |
| 40 | + } |
| 41 | + |
| 42 | + this.cert = undefined; |
| 43 | + this.key = undefined; |
| 44 | + } |
| 45 | + |
| 46 | + async getCertificate() { |
| 47 | + // Create a new keypair and certificate |
| 48 | + let params = {namedCurve: 'P-256'}; // EC parameters |
| 49 | + if (this.signAlgorithm.startsWith("RSA")) { |
| 50 | + // RSA parameters |
| 51 | + params = { |
| 52 | + modulusLength: 2048, |
| 53 | + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), |
| 54 | + hash: this.hashAlgorithm, |
| 55 | + }; |
| 56 | + } |
| 57 | + const keypair = await crypto.subtle.generateKey({ |
| 58 | + name: this.signAlgorithm, |
| 59 | + ...params, |
| 60 | + }, true, ['sign', 'verify']); |
| 61 | + this.cert = await createCertificate(keypair, this.hashAlgorithm); |
| 62 | + this.key = keypair.privateKey; |
| 63 | + return this.cert; |
| 64 | + } |
| 65 | + |
| 66 | + async getSignature(_hash, data) { |
| 67 | + // WebCrypto's sign function automatically computes the hash of the passed data before signing. |
| 68 | + return crypto.subtle.sign({ |
| 69 | + name: this.signAlgorithm, |
| 70 | + hash: this.hashAlgorithm, // Required for ECDSA algorithm |
| 71 | + saltLength: this.saltLengths[this.hashAlgorithm], // Required for RSA-PSS algorithm |
| 72 | + }, this.key, data); |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +function work() { |
| 77 | + // contributing.pdf is the file that is going to be signed |
| 78 | + var sourcePath = path.join(__dirname, '/../../../resources/contributing.pdf'); |
| 79 | + var pdfBuffer = fs.readFileSync(sourcePath); |
| 80 | + |
| 81 | + // Create new CryptoSigner |
| 82 | + var signAlgorithm = 'ECDSA'; |
| 83 | + var hashAlgorithm = 'SHA-512'; |
| 84 | + var signer = new CryptoSigner(signAlgorithm, hashAlgorithm); |
| 85 | + |
| 86 | + // The PDF needs to have a placeholder for a signature to be signed. |
| 87 | + var pdfWithPlaceholder = plainAddPlaceholder({ |
| 88 | + pdfBuffer: pdfBuffer, |
| 89 | + reason: 'The user is declaring consent through JavaScript.', |
| 90 | + contactInfo: 'signpdf@example.com', |
| 91 | + name: 'John Doe', |
| 92 | + location: 'Free Text Str., Free World', |
| 93 | + }); |
| 94 | + |
| 95 | + // pdfWithPlaceholder is now a modified Buffer that is ready to be signed. |
| 96 | + signpdf.sign(pdfWithPlaceholder, signer) |
| 97 | + .then(function (signedPdf) { |
| 98 | + // signedPdf is a Buffer of an electronically signed PDF. Store it. |
| 99 | + var targetPath = path.join(__dirname, '/../output/webcrypto-external.pdf'); |
| 100 | + fs.writeFileSync(targetPath, signedPdf); |
| 101 | + }); |
| 102 | + |
| 103 | +} |
| 104 | + |
| 105 | +work(); |
0 commit comments