libas2
Version:
Implementation of the AS2 protocol as presented in RFC 4130 and related RFCs
169 lines (168 loc) • 7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AS2SignedData = void 0;
const asn1js = require("asn1js");
const pkijs = require("pkijs/build/index");
const webcrypto_1 = require("@peculiar/webcrypto");
const PemFile_1 = require("./PemFile");
const LibOid_1 = require("./LibOid");
const Helpers_1 = require("../Helpers");
const Constants_1 = require("../Constants");
const { SIGNING } = Constants_1.AS2Constants;
const webcrypto = new webcrypto_1.Crypto();
class AS2SignedData {
constructor(data, signedData) {
pkijs.setEngine('newEngine', webcrypto, new pkijs.CryptoEngine({
name: '@peculiar/webcrypto',
crypto: webcrypto,
subtle: webcrypto.subtle
}));
this.data = new Uint8Array(data).buffer;
if (Helpers_1.isNullOrUndefined(signedData)) {
this.signed = new pkijs.SignedData({
version: 1,
encapContentInfo: new pkijs.EncapsulatedContentInfo({
eContentType: new LibOid_1.ObjectID({ name: 'data' }).id
}),
signerInfos: [],
certificates: []
});
}
else {
const bufferBer = new Uint8Array(signedData).buffer;
const signedDataContentAsn1 = asn1js.fromBER(bufferBer);
const signedDataContent = new pkijs.ContentInfo({
schema: signedDataContentAsn1.result
});
this.signed = new pkijs.SignedData({ schema: signedDataContent.content });
}
}
_toCertificate(cert) {
const certPemFile = new PemFile_1.PemFile(cert);
const certAsn1 = asn1js.fromBER(certPemFile.data);
return new pkijs.Certificate({ schema: certAsn1.result });
}
_addSignerInfo(certificate, messageDigest) {
this.signed.certificates.push(certificate);
const position = this.signed.signerInfos.push(new pkijs.SignerInfo({
sid: new pkijs.IssuerAndSerialNumber({
issuer: certificate.issuer,
serialNumber: certificate.serialNumber
}),
signedAttrs: new pkijs.SignedAndUnsignedAttributes({
type: 0,
attributes: [
new pkijs.Attribute({
type: new LibOid_1.ObjectID({ name: 'contentType' }).id,
values: [
new asn1js.ObjectIdentifier({
value: new LibOid_1.ObjectID({ name: 'data' }).id
})
]
}),
new pkijs.Attribute({
type: new LibOid_1.ObjectID({ name: 'signingTime' }).id,
values: [new asn1js.UTCTime({ valueDate: new Date() })]
}),
new pkijs.Attribute({
type: new LibOid_1.ObjectID({ name: 'messageDigest' }).id,
values: [
new asn1js.OctetString({
valueHex: messageDigest
})
]
})
]
})
}));
return position - 1;
}
_getCertAlgorithmId(certificate) {
const rsaPssId = new LibOid_1.ObjectID({ name: 'RSA-PSS' }).id;
if (certificate.signatureAlgorithm.algorithmId === rsaPssId) {
return rsaPssId;
}
return certificate.subjectPublicKeyInfo.algorithm.algorithmId;
}
async _addSigner({ cert, key, algorithm }) {
if (Helpers_1.isNullOrUndefined(algorithm))
algorithm = SIGNING.SHA256;
const crypto = pkijs.getCrypto();
const certificate = this._toCertificate(cert);
const messageDigest = await crypto.digest({ name: algorithm }, this.data);
const privateKeyOptions = crypto.getAlgorithmByOID(this._getCertAlgorithmId(certificate));
if ('hash' in privateKeyOptions) {
privateKeyOptions.hash.name = algorithm;
}
const keyPemFile = new PemFile_1.PemFile(key);
const privateKey = await webcrypto.subtle.importKey('pkcs8', keyPemFile.data, privateKeyOptions, true, ['sign']);
const index = this._addSignerInfo(certificate, messageDigest);
this.digestInfo = {
digest: messageDigest,
algorithm: algorithm
};
await this.signed.sign(privateKey, index, algorithm, this.data);
}
_findSigner(cert) {
if (!Helpers_1.isNullOrUndefined(cert)) {
const certificate = this._toCertificate(cert);
for (let i = 0; i < this.signed.signerInfos.length; i += 1) {
const signerInfo = this.signed.signerInfos[i];
if (certificate.issuer.isEqual(signerInfo.sid.issuer) &&
certificate.serialNumber.isEqual(signerInfo.sid.serialNumber)) {
return i;
}
}
}
return -1;
}
async _calculateMessageDigest(index) {
const crypto = pkijs.getCrypto();
const algorithmId = this.signed.signerInfos[index].digestAlgorithm.algorithmId;
const hashAlgorithm = crypto.getAlgorithmByOID(algorithmId);
this.digestInfo = {
digest: await crypto.digest(hashAlgorithm.name, new Uint8Array(this.data)),
algorithm: hashAlgorithm.name
};
}
getMessageDigest() {
if (typeof this.digestInfo !== 'undefined') {
return {
digest: Buffer.from(this.digestInfo.digest),
algorithm: this.digestInfo.algorithm
};
}
throw new Error('Message digest not yet calculated.');
}
async sign({ cert, key, algorithm, addSigners }) {
await this._addSigner({ cert, key, algorithm });
if (Array.isArray(addSigners)) {
for (const options of addSigners) {
await this._addSigner(options);
}
}
const signedDataContent = new pkijs.ContentInfo({
contentType: new LibOid_1.ObjectID({ name: 'signedData' }).id,
content: this.signed.toSchema(true)
});
const signedDataSchema = signedDataContent.toSchema();
const signedDataBuffer = signedDataSchema.toBER();
return Buffer.from(signedDataBuffer);
}
async verify(cert, debugMode) {
const index = this._findSigner(cert);
if (!Helpers_1.isNullOrUndefined(cert) && index === -1) {
return false;
}
const result = await this.signed.verify({
signer: index === -1 ? 0 : index,
data: this.data,
extendedMode: true
});
if (result.signatureVerified) {
await this._calculateMessageDigest(index);
}
return debugMode ? result : result.signatureVerified;
}
}
exports.AS2SignedData = AS2SignedData;