Подписывать PDF-файлы в современном браузере изначально? - PullRequest
1 голос
/ 19 июня 2020

Чего я пытаюсь достичь

  • Подписать PDF-файл в браузере, используя хранилище сертификатов cliets или смарт-карту

Что я сделал до сих пор

  1. Для доступа к локальному хранилищу сертификатов я использую FortifyApp .
  2. 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:

Я пробовал несколько сертификатов, не повезло.

Вопросы

  1. Могу ли я добиться моя цель без необходимости вручную загружать файл p12 / pfx, возможно ли это?
  2. Правильна ли серверная реализация отложенной подписи, нужно ли мне что-то еще? pdf манипуляция в javascript правильно?
  3. Можно ли переделать родную CrytpoKey в forge или pki js?
  4. Что не так с последней подписью? На первый взгляд кажется правильным (по крайней мере, формат):

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

Спасибо: F

1 Ответ

0 голосов
/ 29 июня 2020

Итак, я понял.

Могу ли я достичь своей цели без необходимости вручную загружать файл p12 / pfx, возможно ли это вообще?

Да, это. (См. Ниже, что необходимо изменить.)

Правильна ли реализация отложенной подписи на стороне сервера, нужно ли мне что-то еще?

Да, код выше в порядке.

Правильно ли манипулирование pdf в javascript?

Также нормально. CrytpoKey для подделки или pki js?

Да, см. Ниже.

Что не так с последней подписью?

@ mkl ответил на это в комментарии, спасибо.

FortifyApp теперь имеет CMS demo . Хотя он не работал с версией, которую я использовал, он работает с версией 1.3.4.

Поэтому я выбрал реализацию pki. js. Для успешной подписи необходимы следующие изменения кода:

  1. Экспорт сертификата:
const cryptoCert = await provider.certStorage.getItem(selectedCertificateId);
const certRawData = await provider.certStorage.exportCert('raw', cryptoCert);

const pkiCert = new pki.Certificate({
    schema: asn.fromBER(certRawData).result,
});

return pkiCert;
Вход в автономном режиме
let cmsSigned = new pki.SignedData({
    version: 1,
    encapContentInfo: new pki.EncapsulatedContentInfo({
        eContentType: '1.2.840.113549.1.7.1',
    }),
    signerInfos: [
        new pki.SignerInfo({
            version: 1,
            sid: new pki.IssuerAndSerialNumber({
                issuer: certificate.issuer,
                serialNumber: certificate.serialNumber
            })
        })
    ],
    certificates: [certificate]
});

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

const cms = new pki.ContentInfo({
    contentType: '1.2.840.113549.1.7.2',
    content: cmsSigned.toSchema(true),
});
const result = cms.toSchema().toBER(false);

return result;
Преобразовать подпись в строку 'HEX'
let stringSignature = Array.prototype.map.call(new Uint8Array(signature), x => (`00${x.toString(16)}`).slice(-2)).join('');
let len = signature.byteLength;
...