signature=dcc2cbece4b59f05f1d754434a56a996,javascript - Sign PDF in a modern browser natively? - Sta...

What I'm trying to achieve

Sign a PDF in the browser using cliets certificate store or Smart Card

What I did so far

For accessing the local cert store I use FortifyApp.

Pdf is pre-signed on the server using iText(Sharp), then sent to the client via Ajax.

Relevant code:

using (var fileStream = new MemoryStream())

{

using (var stamper = PdfStamper.CreateSignature(reader, fileStream, '0', null, true))

{

var signatureAppearance = stamper.SignatureAppearance;

signatureAppearance.SetVisibleSignature(new iTextSharp.text.Rectangle(15,15,15,15), 1, "A");

IExternalSignatureContainer external =

new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);

signatureAppearance.Reason = "AsdAsd";

signatureAppearance.Layer2Text = "Asd";

signatureAppearance.SignatureRenderingMode =

iTextSharp.text.pdf.PdfSignatureAppearance.RenderingMode.DESCRIPTION;

MakeSignature.SignExternalContainer(signatureAppearance, external, 512);

return fileStream.ToArray();

}

}

Following this, I managed to manipulate the pdf, extract byteRange, insert signature, etc. Relevant code:

let pdfBuffer = Buffer.from(new Uint8Array(pdf));

const byteRangeString = `/ByteRange `;

const byteRangePos = pdfBuffer.indexOf(byteRangeString);

if (byteRangePos === -1)

throw new Error('asd');

let len = pdfBuffer.slice(byteRangePos).indexOf(`]`) + 1;

// Calculate the actual ByteRange that needs to replace the placeholder.

const byteRangeEnd = byteRangePos + len;

const contentsTagPos = pdfBuffer.indexOf('/Contents ', byteRangeEnd);

const placeholderPos = pdfBuffer.indexOf('

const placeholderEnd = pdfBuffer.indexOf('>', placeholderPos);

const placeholderLengthWithBrackets = placeholderEnd + 1 - placeholderPos;

const placeholderLength = placeholderLengthWithBrackets - 2;

const byteRange = [0, 0, 0, 0];

byteRange[1] = placeholderPos;

byteRange[2] = byteRange[1] + placeholderLengthWithBrackets;

byteRange[3] = pdfBuffer.length - byteRange[2];

let actualByteRange = `/ByteRange [${byteRange.join(' ')}]`;

actualByteRange += ' '.repeat(len - actualByteRange.length);

// Replace the /ByteRange placeholder with the actual ByteRange

pdfBuffer = Buffer.concat([pdfBuffer.slice(0, byteRangePos) as any, Buffer.from(actualByteRange), pdfBuffer.slice(byteRangeEnd)]);

// Remove the placeholder signature

pdfBuffer = Buffer.concat([pdfBuffer.slice(0, byteRange[1]) as any, pdfBuffer.slice(byteRange[2], byteRange[2] + byteRange[3])]);

and

//stringSignature comes from the signature creations below, and is 'hex' encoded

// Pad the signature with zeroes so the it is the same length as the placeholder

stringSignature += Buffer

.from(String.fromCharCode(0).repeat((placeholderLength / 2) - len))

.toString('hex');

// Place it in the document.

pdfBuffer = Buffer.concat([

pdfBuffer.slice(0, byteRange[1]) as any,

Buffer.from(``),

pdfBuffer.slice(byteRange[1])

]);

The problem

This uses forge, and an uploaded p12 file. - This would probably work, if I could translate the imported(?) privateKey from Fortify (which is === typeof CryptoKey, and forge throws an error: TypeError: signer.key.sign is not a function).

p7.addCertificate(certificate); //certificate is the Certificate from Fortify CertificateStore.getItem(certId)

p7.addSigner({

key: privateKey, //this is the CryptoKey from Fortify

certificate: null/*certificate*/, //also tried certificate from Fortify

digestAlgorithm: forge.pki.oids.sha256,

authenticatedAttributes: [

{

type: forge.pki.oids.contentType,

value: forge.pki.oids.data,

}, {

type: forge.pki.oids.messageDigest,

// value will be auto-populated at signing time

}, {

type: forge.pki.oids.signingTime,

// value can also be auto-populated at signing time

// We may also support passing this as an option to sign().

// Would be useful to match the creation time of the document for example.

value: new Date(),

},

],

});

// Sign in detached mode.

p7.sign({detached: true});

I also tried pkijs for creating the signature (throws a similar error: Signing error: TypeError: Failed to execute 'sign' on 'SubtleCrypto': parameter 2 is not of type 'CryptoKey'.)

let cmsSigned = new pki.SignedData({

encapContentInfo: new pki.EncapsulatedContentInfo({

eContentType: "1.2.840.113549.1.7.1", // "data" content type

eContent: new asn.OctetString({ valueHex: pdfBuffer })

}),

signerInfos: [

new pki.SignerInfo({

sid: new pki.IssuerAndSerialNumber({

issuer: certificate.issuer,

serialNumber: certificate.serialNumber

})

})

],

certificates: [certificate]

});

let signature = await cmsSigned.sign(privateKey, 0, 'SHA-256');

What "works" is, if I create the signature using the code below:

let signature = await provider.subtle.sign(alg, privateKey, new Uint8Array(pdfBuffer).buffer);

"works", because it creates an invalid signature:

Error during signature verification.

ASN.1 parsing error:

Error encountered while BER decoding:

I tried multiple certificates, no luck.

Questions

Can I achieve my goal without having to manually upload a p12/pfx file, is it even possible?

Is the server-side implementation of the deferred signature correct, do I need something else?

Is the pdf manipulation in javascript correct?

Can I transform the native CrytpoKey to forge or pkijs?

What is wrong with the last signature? At first glance it seems right (at least the format):

<>>>/ContactInfo()/M(D:20200619143454+02'00')/Filter/Adobe.PPKLite/SubFilter/adbe.pkcs7.detached/ByteRange [0 180165 181191 1492] /Contents <72eb2731c9de4a5ccc94f1e1f2d9b07be0c6eed8144cb73f3dfe2764595dcc8f58b8a55f5026618fd9c79146ea93afdafc00b617c6e70de553600e4520f290bef70c499ea91862bb3acc651b6a7b162c984987f05ec59db5b032af0127a1224cad82e3be38ae74dd110ef5f870f0a0a92a8fba295009f267508c372db680b3d89d3157d3b218f33e7bf30c500d599b977c956e6a6e4b02a0bbd4a86737378b421ae2af0a4a3c03584eaf076c1cdb56d372617da06729ef364605ecd98b6b32d3bb792b4541887b59b686b41db3fc32eb4c651060bb02e2babeb30e6545834b2935993f6ee9edcc8f99fee8ad6edd2958c780177df6071fdc75208f76bbbcc21a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000>>>

Thanks:

F

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值