refactor: extract createSigner factory and add signature algorithm OID

pull/14/head
Marcos Sanz Latorre 2026-08-04 22:12:14 +02:00
parent db7b45d445
commit 179fdf11e4
3 changed files with 101 additions and 23 deletions

View File

@ -240,6 +240,13 @@ z.RsaSigner = class{
return forge.pki.oids.sha256; 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 * @param {string} data
* @return {string} raw signature bytes * @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{ z.PdfSigner = class{
/** /**
* @param {SignOption} signopt * @param {SignOption} signopt
@ -1053,7 +1076,7 @@ z.PdfSigner = class{
// so it can later be swapped without touching this code. node-forge still // so it can later be swapped without touching this code. node-forge still
// performs the RSA signing via the supplied key (shallow seam). // performs the RSA signing via the supplied key (shallow seam).
/** @type {z.RsaSigner} */ /** @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. // Add the signer. sha256 is what Adobe.PPKLite adbe.pkcs7.detached expects.
p7.addSigner({ p7.addSigner({

View File

@ -197,8 +197,15 @@ async function main1(angle){
// test urlFetch // test urlFetch
async function main2(){ async function main2(){
// 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} */ /** @type {Uint8Array} */
var u8arr = await Zga.urlFetch("http://localhost:8080", { var u8arr = await Zga.urlFetch("http://localhost:"+port, {
"headers": { "headers": {
"testzb": "pineapple" "testzb": "pineapple"
} }
@ -210,10 +217,18 @@ async function main2(){
/** @type {string} */ /** @type {string} */
var str = txtdec.decode(u8arr); var str = txtdec.decode(u8arr);
console.log(str); 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<http.Server>} The listening server.
*/
function startWebserver(port){
/** @type {http.Server} */
var srv = require("http").createServer(function(req, res){
if(req.method == "GET"){ if(req.method == "GET"){
if(req.headers["testzb"]){ if(req.headers["testzb"]){
res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Origin", "*");
@ -241,7 +256,14 @@ function webserver(){
res.statusMessage = "CORS OK"; res.statusMessage = "CORS OK";
res.end(); 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(){ async function main(){
@ -254,10 +276,21 @@ async function main(){
} }
} }
if(process.argv[2] == "webserver"){ /**
webserver(); * @param {Promise<*>} p
}else if(process.argv[2] == "fetch"){ */
main2(); function run(p){
}else{ p.catch(function(err){
main(); 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());
} }

View File

@ -28,3 +28,25 @@ test("RsaSigner reports SHA-256 as its digest algorithm OID", () => {
assert.strictEqual(signer.getDigestAlgorithmOid(), forge.pki.oids.sha256); 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/,
);
});