Чего я пытаюсь достичь
- Подписать PDF-файл в браузере, используя хранилище сертификатов cliets или смарт-карту
Что я сделал до сих пор
- Для доступа к локальному хранилищу сертификатов я использую FortifyApp .
- PDF-файл предварительно подписан на сервере с помощью iText (Sharp), а затем отправляется клиенту через Ajax . Соответствующий код:
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();
}
}
После
этого мне удалось манипулировать pdf, извлечь byteRange, вставить подпись и т. Д. c. Соответствующий код:
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('<', contentsTagPos);
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])]);
и
//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(`<${stringSignature}>`),
pdfBuffer.slice(byteRange[1])
]);
Проблема
- Этот использует кузницу и загруженный файл p12. - Это, вероятно, сработало бы, если бы я мог перевести импортированный (?) PrivateKey из Fortify (это
=== typeof CryptoKey
, а forge выдает ошибку: 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});
- Я также пробовал pki js для создания подписи (выдает аналогичную ошибку:
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');
- Что "работает", если Я создаю подпись, используя приведенный ниже код:
let signature = await provider.subtle.sign(alg, privateKey, new Uint8Array(pdfBuffer).buffer);
«работает», потому что создает недопустимую подпись:
Ошибка при проверке подписи.
ASN .1 ошибка анализа:
Ошибка при декодировании BER:
Я пробовал несколько сертификатов, не повезло.
Вопросы
- Могу ли я добиться моя цель без необходимости вручную загружать файл p12 / pfx, возможно ли это?
- Правильна ли серверная реализация отложенной подписи, нужно ли мне что-то еще? pdf манипуляция в javascript правильно?
- Можно ли переделать родную
CrytpoKey
в forge или pki js? - Что не так с последней подписью? На первый взгляд кажется правильным (по крайней мере, формат):
<>>> / ContactInfo () / M (D: 20200619143454 + 02'00 ') / Filter / Adobe. PPKLite / SubFilter / adbe.pkcs7.detached / ByteRange [0 180165 181191 1492] / Contents <>>>
Спасибо: F