diff --git a/lib/zgapdfsigner.js b/lib/zgapdfsigner.js index 3e5da8c..e8480ed 100644 --- a/lib/zgapdfsigner.js +++ b/lib/zgapdfsigner.js @@ -240,6 +240,13 @@ z.RsaSigner = class{ return forge.pki.oids.sha256; } + /** + * @return {string} the signature algorithm OID for the PKCS#7 SignerInfo + */ + getSignatureAlgorithmOid(){ + return forge.pki.oids.sha256WithRSAEncryption; + } + /** * @param {string} data * @return {string} raw signature bytes @@ -252,6 +259,22 @@ z.RsaSigner = class{ } }; +/** + * Select a Signer implementation for the given key. Today only RSA keys are + * supported; this factory is the single place to extend the seam with ECDSA + * or post-quantum signers without touching the PDF assembly. + * + * @param {forge.pki.rsa.PrivateKey} privateKey + * @param {forge_cert} certificate + * @return {z.RsaSigner} + */ +z.createSigner = function(privateKey, certificate){ + if(privateKey && privateKey.n && privateKey.e){ + return new z.RsaSigner(privateKey, certificate); + } + throw new Error("Unsupported signing key type; only RSA keys are supported."); +}; + z.PdfSigner = class{ /** * @param {SignOption} signopt @@ -1053,7 +1076,7 @@ z.PdfSigner = class{ // so it can later be swapped without touching this code. node-forge still // performs the RSA signing via the supplied key (shallow seam). /** @type {z.RsaSigner} */ - var signer = new z.RsaSigner(_this.privateKey, _this.cchain.getSignCert()); + var signer = z.createSigner(_this.privateKey, _this.cchain.getSignCert()); // Add the signer. sha256 is what Adobe.PPKLite adbe.pkcs7.detached expects. p7.addSigner({ diff --git a/test4node.js b/test4node.js index ecd712c..38d1fad 100644 --- a/test4node.js +++ b/test4node.js @@ -197,23 +197,38 @@ async function main1(angle){ // test urlFetch async function main2(){ - /** @type {Uint8Array} */ - var u8arr = await Zga.urlFetch("http://localhost:8080", { - "headers": { - "testzb": "pineapple" - } - }); - // /** @type {string} */ - // var str = btoa(Zga.u8arrToRaw(u8arr)); - /** @type {TextDecoder} */ - var txtdec = new TextDecoder("utf-8"); - /** @type {string} */ - var str = txtdec.decode(u8arr); - console.log(str); + // Boot a throwaway server on an ephemeral port so this test is self + // contained and cannot collide with an already running `npm run server`. + /** @type {http.Server} */ + var srv = await startWebserver(0); + /** @type {number} */ + var port = srv.address().port; + try{ + /** @type {Uint8Array} */ + var u8arr = await Zga.urlFetch("http://localhost:"+port, { + "headers": { + "testzb": "pineapple" + } + }); + // /** @type {string} */ + // var str = btoa(Zga.u8arrToRaw(u8arr)); + /** @type {TextDecoder} */ + var txtdec = new TextDecoder("utf-8"); + /** @type {string} */ + var str = txtdec.decode(u8arr); + console.log(str); + }finally{ + srv.close(); + } } -function webserver(){ - require("http").createServer(function(req, res){ +/** + * @param {number} port Port to listen on. 0 picks a free ephemeral port. + * @return {Promise} The listening server. + */ +function startWebserver(port){ + /** @type {http.Server} */ + var srv = require("http").createServer(function(req, res){ if(req.method == "GET"){ if(req.headers["testzb"]){ res.setHeader("Access-Control-Allow-Origin", "*"); @@ -241,7 +256,14 @@ function webserver(){ res.statusMessage = "CORS OK"; res.end(); } - }).listen(8080, function(){console.log("Server http://localhost:8080")}); + }); + return new Promise(function(resolve, reject){ + srv.once("error", reject); + srv.listen(port, function(){ + console.log("Server http://localhost:"+srv.address().port); + resolve(srv); + }); + }); } async function main(){ @@ -254,10 +276,21 @@ async function main(){ } } -if(process.argv[2] == "webserver"){ - webserver(); -}else if(process.argv[2] == "fetch"){ - main2(); -}else{ - main(); +/** + * @param {Promise<*>} p + */ +function run(p){ + p.catch(function(err){ + console.error(err); + process.exitCode = 1; + }); +} + +if(process.argv[2] == "webserver"){ + // Fixed port, because test.html expects the server on 8080. + run(startWebserver(8080)); +}else if(process.argv[2] == "fetch"){ + run(main2()); +}else{ + run(main()); } diff --git a/tests/signer.test.js b/tests/signer.test.js index 8b81bb7..f3f1e6e 100644 --- a/tests/signer.test.js +++ b/tests/signer.test.js @@ -28,3 +28,25 @@ test("RsaSigner reports SHA-256 as its digest algorithm OID", () => { assert.strictEqual(signer.getDigestAlgorithmOid(), forge.pki.oids.sha256); }); + +test("RsaSigner reports sha256WithRSAEncryption as its signature algorithm OID", () => { + const {privateKey, certificate} = makeKeyCert(3072); + const signer = new Zga.RsaSigner(privateKey, certificate); + + assert.strictEqual(signer.getSignatureAlgorithmOid(), forge.pki.oids.sha256WithRSAEncryption); +}); + +test("createSigner returns an RsaSigner for an RSA key", () => { + const {privateKey, certificate} = makeKeyCert(3072); + + const signer = Zga.createSigner(privateKey, certificate); + + assert.ok(signer instanceof Zga.RsaSigner); +}); + +test("createSigner rejects an unsupported (non-RSA) key type", () => { + assert.throws( + () => Zga.createSigner({}, null), + /only RSA keys are supported/, + ); +});