cadesjs
Version:
CAdESjs gives you an ability to make CAdES signatures on pure JavaScript. The libray made with latest CAdES standards in mind
324 lines (279 loc) • 11.4 kB
JavaScript
import * as asn1js from "asn1js";
import { getParametersValue, utilConcatBuf } from "pvutils";
import { getCrypto, ContentInfo, SignedData, IssuerAndSerialNumber, Attribute, TimeStampResp, SignedAndUnsignedAttributes } from "pkijs";
import ATSHashIndex from "./ATSHashIndex.js";
//**************************************************************************************
// noinspection JSUnusedGlobalSymbols
export default class ArchiveTimeStampV3 extends ContentInfo
{
//**********************************************************************************
/**
* Constructor for ArchiveTimeStampV3 class
* @param {Object} [parameters={}]
* @property {Object} [schema] asn1js parsed value
*/
constructor(parameters = {})
{
super(parameters);
//region Internal properties of the object
if("tspResponse" in parameters)
/**
* @type {ArrayBuffer}
* @description tspResponse
*/
this.tspResponse = getParametersValue(parameters, "tspResponse", ArchiveTimeStampV3.defaultValues("tspResponse"));
/**
* @type {ATSHashIndex}
* @description aTSHashIndex
*/
this.aTSHashIndex = getParametersValue(parameters, "aTSHashIndex", ArchiveTimeStampV3.defaultValues("aTSHashIndex"));
//endregion
//region If input argument array contains "schema" for this object
if("schema" in parameters)
this.fromSchema(parameters.schema);
//endregion
}
//**********************************************************************************
/**
* Return default values for all class members
* @param {string} memberName String name for a class member
*/
static defaultValues(memberName)
{
switch(memberName)
{
case "tspResponse":
return new ArrayBuffer(0);
case "aTSHashIndex":
return new ATSHashIndex();
default:
throw new Error(`Invalid member name for ArchiveTimeStampV3 class: ${memberName}`);
}
}
//**********************************************************************************
/**
* Compare values with default values for all class members
* @param {string} memberName String name for a class member
* @param {*} memberValue Value to compare with default value
*/
static compareWithDefault(memberName, memberValue)
{
switch(memberName)
{
case "tspResponse":
return (memberValue.byteLength === 0);
case "aTSHashIndex":
// noinspection OverlyComplexBooleanExpressionJS
return ((("hashIndAlgorithm" in memberValue) === false) &&
(ATSHashIndex.compareWithDefault("certificatesHashIndex", memberValue.certificatesHashIndex)) &&
(ATSHashIndex.compareWithDefault("crlsHashIndex", memberValue.crlsHashIndex)) &&
(ATSHashIndex.compareWithDefault("unsignedAttrsHashIndex", memberValue.unsignedAttrsHashIndex)));
default:
throw new Error(`Invalid member name for ArchiveTimeStampV3 class: ${memberName}`);
}
}
//**********************************************************************************
/**
* Convert parsed asn1js object into current class
* @param {!Object} schema
* @param {boolean} [initValues=true]
*/
fromSchema(schema, initValues = true)
{
super.fromSchema(schema);
if(initValues)
{
if(this.contentType !== "1.2.840.113549.1.7.2")
throw new Error("Incorrect object schema for archive-time-stamp-v3 attribute: incorrect content type");
this.tspResponse = new TimeStampResp({ timeStampToken: schema });
const cmsSignedData = new SignedData({ schema: this.content });
if(cmsSignedData.signerInfos.length !== 1)
throw new Error("Incorrect object schema for archive-time-stamp-v3 attribute: incorrect signerInfos length");
if(("unsignedAttrs" in cmsSignedData.signerInfos[0]) === false)
throw new Error("Incorrect object schema for archive-time-stamp-v3 attribute: missing unsignedAttrs");
if(cmsSignedData.signerInfos[0].unsignedAttrs.attributes.length !== 1)
throw new Error("Incorrect object schema for archive-time-stamp-v3 attribute: incorrect unsignedAttrs length");
const attribute = new Attribute(cmsSignedData.signerInfos[0].unsignedAttrs.attributes[0]);
if(attribute.type !== "0.4.0.1733.2.5")
throw new Error("Incorrect object schema for archive-time-stamp-v3 attribute: incorrect type for aTSHashIndex value");
let parsedValue;
try
{
parsedValue = new ATSHashIndex({ schema: attribute.values[0] });
}
catch(e)
{
throw new Error("Incorrect object schema for archive-time-stamp-v3 attribute: incorrect aTSHashIndex value");
}
this.aTSHashIndex = parsedValue;
}
}
//**********************************************************************************
// noinspection JSUnusedGlobalSymbols
/**
* Get "ArrayBuffer" to transfer to time-stamp server
* @param {SignedData} cmsSignedData CMS Signed Data to make attribute for
* @param {number} signerIndex Index of signer to make attribute for
* @param {Object} parameters Additional parameters for making attribute
* @returns {Promise}
*/
getStampingBuffer(cmsSignedData, signerIndex, parameters)
{
//region Initial variables
let sequence = Promise.resolve();
let hashAlgorithm = "SHA-256";
let content = new ArrayBuffer(0);
let aTSHashIndex = new ArrayBuffer(0);
let resultBuffer = new ArrayBuffer(0);
//endregion
//region Get a "crypto" extension
const crypto = getCrypto();
if(typeof crypto === "undefined")
return Promise.reject("Unable to create WebCrypto object");
//endregion
//region Check input parameters
if("hashAlgorithm" in parameters)
hashAlgorithm = parameters.hashAlgorithm;
if("content" in parameters)
content = parameters.content; // ArrayBuffer
else
{
if("eContent" in cmsSignedData.encapContentInfo)
{
if((cmsSignedData.encapContentInfo.eContent.idBlock.tagClass === 1) &&
(cmsSignedData.encapContentInfo.eContent.idBlock.tagNumber === 4))
{
if(cmsSignedData.encapContentInfo.eContent.idBlock.isConstructed === false)
content = cmsSignedData.encapContentInfo.eContent.valueBlock.valueHex;
else
{
for(let i = 0; i < cmsSignedData.encapContentInfo.eContent.valueBlock.value.length; i++)
content = utilConcatBuf(content, cmsSignedData.encapContentInfo.eContent.valueBlock.value[i].valueBlock.valueHex);
}
}
else
content = cmsSignedData.encapContentInfo.eContent.valueBlock.valueHex;
}
else
return Promise.reject("Parameter \"content\" is mandatory for making archive-time-stamp-v3");
}
if("aTSHashIndex" in parameters)
aTSHashIndex = parameters.aTSHashIndex; // ArrayBuffer
else
return Promise.reject("Parameter \"aTSHashIndex\" is mandatory for making archive-time-stamp-v3");
//endregion
//region Make hash of initial content
sequence = sequence.then(
() => crypto.digest({ name: hashAlgorithm }, content),
error => Promise.reject(error)
);
//endregion
//region Make final stamping buffer
sequence = sequence.then(
result => {
//region eContentType
const contentOID = new asn1js.ObjectIdentifier({ value: cmsSignedData.encapContentInfo.eContentType });
resultBuffer = utilConcatBuf(resultBuffer, contentOID.toBER(false));
//endregion
//region message-digest
// noinspection JSCheckFunctionSignatures
resultBuffer = utilConcatBuf(resultBuffer, result);
//endregion
//region version
const version = new asn1js.Integer({ value: cmsSignedData.signerInfos[signerIndex].version });
resultBuffer = utilConcatBuf(resultBuffer, version.toBER(false));
//endregion
//region sid
let sid;
if(cmsSignedData.signerInfos[signerIndex].sid instanceof IssuerAndSerialNumber)
sid = cmsSignedData.signerInfos[signerIndex].sid.toSchema();
else
sid = cmsSignedData.signerInfos[signerIndex].sid;
resultBuffer = utilConcatBuf(resultBuffer, sid.toBER(false));
//endregion
//region digestAlgorithm
resultBuffer = utilConcatBuf(resultBuffer, cmsSignedData.signerInfos[signerIndex].digestAlgorithm.toSchema().toBER(false));
//endregion
//region signedAttrs
if("signedAttrs" in cmsSignedData.signerInfos[signerIndex])
resultBuffer = utilConcatBuf(resultBuffer, cmsSignedData.signerInfos[signerIndex].signedAttrs.toSchema().toBER(false));
else
return Promise.reject("Must be \"signedAttrs\" inside SignerInfo structure. Check correctness of the signed data.");
//endregion
//region signatureAlgorithm
resultBuffer = utilConcatBuf(resultBuffer, cmsSignedData.signerInfos[signerIndex].signatureAlgorithm.toSchema().toBER(false));
//endregion
//region signature
resultBuffer = utilConcatBuf(resultBuffer, cmsSignedData.signerInfos[signerIndex].signature.toBER(false));
//endregion
//region ATSHashIndexV2
resultBuffer = utilConcatBuf(resultBuffer, aTSHashIndex);
//endregion
return resultBuffer;
},
error => Promise.reject(error)
);
//endregion
//region Make hash of result buffer
sequence = sequence.then(
result => crypto.digest({ name: hashAlgorithm }, result),
error => Promise.reject(error)
);
//endregion
return sequence;
}
//**********************************************************************************
/**
* Create "archive-time-stamp-v3" CAdES attribute
* @param {Object} [parameters] Additional parameters for making attribute
* @returns {Attribute}
*/
makeAttribute(parameters = {})
{
//region Initial variables
let tspResponse;
//endregion
//region Check input parameters
if("tspResponse" in parameters)
tspResponse = parameters.tspResponse;
else
{
if("tspResponse" in this)
tspResponse = this.tspResponse;
else
throw new Error("Parameter \"tspResponse\" is mandatory for making archive-time-stamp-v3 attribute");
}
this.tspResponse = tspResponse;
//endregion
//region Change type of "tspResponse"
const asn1 = asn1js.fromBER(tspResponse);
tspResponse = new TimeStampResp({ schema: asn1.result });
//endregion
//region Initialize internal variables from "tspResponse"
if("timeStampToken" in tspResponse)
this.fromSchema(tspResponse.timeStampToken.toSchema(), false);
else
throw new Error("No neccessary \"timeStampToken\" inside \"tspResponse\"");
//endregion
//region Append "ATSHashIndex" into local unsigned attributes
const cmsSignedData = new SignedData({ schema: this.content });
cmsSignedData.signerInfos[0].unsignedAttrs = new SignedAndUnsignedAttributes({
type: 1, // UnsignedAttributes
attributes: [
this.aTSHashIndex.makeAttribute()
]
});
this.content = cmsSignedData.toSchema();
//endregion
//region Create and return attribute
return new Attribute({
type: "0.4.0.1733.2.4",
values: [
this.toSchema()
]
});
//endregion
}
//**********************************************************************************
}
//**************************************************************************************