PeculiarVentures / PKI.js

PKI.js is a pure JavaScript library implementing the formats that are used in PKI applications (signing, encryption, certificate requests, OCSP and TSP requests/responses). It is built on WebCrypto (Web Cryptography API) and requires no plug-ins.
http://pkijs.org
Other
1.25k stars 204 forks source link

CMS signature: signedAttrs should use DER encoding instead of BER #402

Open mholy-arbes opened 3 months ago

mholy-arbes commented 3 months ago

Hello, according to the RFC 5652, when using SignedAttrs, the message digest should be calculated as

the complete DER encoding of the SignedAttrs value contained in the signedAttrs field

However, this is not happening right now as per code in SignedData.ts which uses BER encoding:

if (signerInfo.signedAttrs) {
      if (signerInfo.signedAttrs.encodedValue.byteLength !== 0)
        data = signerInfo.signedAttrs.encodedValue;
      else {
        data = signerInfo.signedAttrs.toSchema().toBER();

        //#region Change type from "[0]" to "SET" accordingly to standard
        const view = pvtsutils.BufferSourceConverter.toUint8Array(data);
        view[0] = 0x31;
        //#endregion
      }
    }

The main problem that I have is that when passing attributes into the SignedAndUnsignedAttributes, the message digest depends on the order I specify the attributes. I believe this should not be happening and the message digest should be the same, independent of the attributes order.

This causes problems when verifying the signature with Java Bouncy Castle library, which expects the DER (ordered) encoding of the signed attributes.

Examples

This attribute order does not validate

const signedAttrs = new SignedAndUnsignedAttributes({
    type: 0,
    attributes: [
        new Attribute({
            type: OID.ContentType,
            values: [new asn1js.ObjectIdentifier({ value: OID.Data })],
        }),
        new Attribute({
            type: OID.MessageDigest,
            values: [new asn1js.OctetString({ valueHex: dataDigest })],
        }),
        new Attribute({
            type: OID.SigningTime,
            values: [new asn1js.UTCTime({ valueDate: new Date() })],
        }),
    ],
});

Switching positions of SigningTime and MessageDigest produces a valid signature

const signedAttrs = new SignedAndUnsignedAttributes({
    type: 0,
    attributes: [
        new Attribute({
            type: OID.ContentType,
            values: [new asn1js.ObjectIdentifier({ value: OID.Data })],
        }),
        new Attribute({
            type: OID.SigningTime,
            values: [new asn1js.UTCTime({ valueDate: new Date() })],
        }),
        new Attribute({
            type: OID.MessageDigest,
            values: [new asn1js.OctetString({ valueHex: dataDigest })],
        }),
    ],
});