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

can not validate schema against SignedData error for (RPKI) cms that openssl accepts #324

Closed ties closed 2 years ago

ties commented 2 years ago

I am trying to parse an Resource Public Key Infrastructure (RPKI) manifest (RFC6486) which are ASN.1 structures in CMS.

Uncaught Error: Object's schema was not verified against input data for SignedData
    at SignedData.fromSchema (CMSSigned_complex_example.js:formatted:30602)
    at new SignedData (CMSSigned_complex_example.js:formatted:30461)
    at parseCMSSigned (CMSSigned_complex_example.js:formatted:31888)
    at FileReader.tempReader.onload (CMSSigned_complex_example.js:formatted:32368)

When I do so, I get the exception above with a root cause of Wrong values for Choice type (likely because of SignerInfo.sid - but debugging this is non-trivial).

Since openssl accepts the CMS (as seen below) and BouncyCastle also accepts them I think they are valid. Could you take a look?

openssl example

rsync rsync://rpki.arin.net/repository/arin-rpki-ta/arin-rpki-ta.mft .
rsync rsync://rpki.arin.net/repository/arin-rpki-ta.cer .

openssl x509 -in arin-rpki-ta.cer -inform DER -out arin-rpki-ta.cer.pem
openssl cms -verify -inform DER -in arin-rpki-ta.mft -CAfile arin-rpki-ta.cer.pem
# => Verification successful

rsync rsync://rpki.ripe.net/repository/ripe-ncc-ta.mft .
rsync rsync://rpki.ripe.net/ta/ripe-ncc-ta.cer .

openssl x509 -in ripe-ncc-ta.cer -inform DER -out ripe-ncc-ta.cer.pem
openssl cms -verify -inform DER -in ripe-ncc-ta.mft -CAfile ripe-ncc-ta.cer.pem
# => Verification successful
ties commented 2 years ago

The case that applies is (from https://datatracker.ietf.org/doc/html/rfc5652)

      SignerInfo ::= SEQUENCE {
        version CMSVersion,
        sid SignerIdentifier,
        digestAlgorithm DigestAlgorithmIdentifier,
        signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
        signatureAlgorithm SignatureAlgorithmIdentifier,
        signature SignatureValue,
        unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }

      SignerIdentifier ::= CHOICE {
        issuerAndSerialNumber IssuerAndSerialNumber,
        subjectKeyIdentifier [0] SubjectKeyIdentifier }

...

      SubjectKeyIdentifier ::= OCTET STRING

Implemented at https://github.com/PeculiarVentures/PKI.js/blob/master/src/SignerInfo.js#L185 . This is where my debugging ended.

rmhrisk commented 2 years ago

Please attach reference objects to the bug. You may also want to look at https://www.npmjs.com/package/@peculiar/x509 for the decode case. I suspect it will work better.

ties commented 2 years ago

I have attached the objects: rpki-manifests-and-certs.zip

In the end I wanted to parse a specific CMS profile's (https://datatracker.ietf.org/doc/html/rfc6488) eContent to build a tiny debugging tool, so I would expect PKI.js or asn1.js is the suitable library for this. For the current use case I do not need to check the CMS signature, so I could use asn1.js, but ideally it should.

https://www.npmjs.com/package/@peculiar/x509 looks very usable for decoding the certificate. Edit: asn1-schema may also work very nicely for this use case

microshine commented 2 years ago

@ties Thank you for this issue

I reproduced this error using both pkijs and @peculiar/asn1-cms modules.

I fixed and published @peculiar/asn1-cms please try version 2.0.37. It parses CMS (from your ZIP file) without exception

import { AsnConvert, AsnParser } from "@peculiar/asn1-schema";
import { ContentInfo, SignedData } from "@peculiar/asn1-cms";

const rpkiMftB64 = "MIIHegYJKoZIhvcNAQcCoIIHazCCB2cCAQMxDzANBglghkgBZQMEAgEFADCB4gYLKoZIhvcNAQkQARqg" +
  // ...
  "TYbY3LQ33K7LWpVJwB9Oalvx6dvvU9zhwX+nE6oLPSkwbC5J9OIdIq8W62aEBsZ8qRJU79a/JM676Q==";
const rpkiMftBuffer = Buffer.from(rpkiMftB64, "base64");

const contentInfo = AsnConvert.parse(rpkiMftBuffer, ContentInfo);
const signedData = AsnConvert.parse(contentInfo.content, SignedData);

Working on pkijs fix

microshine commented 2 years ago

pkijs declares subjectKeyIdentifier like EXPLICIT but it must be IMPLICIT

   DEFINITIONS IMPLICIT TAGS ::=

   SignerIdentifier ::= CHOICE {
     issuerAndSerialNumber IssuerAndSerialNumber,
     subjectKeyIdentifier [0] SubjectKeyIdentifier }

Fix

        return (
            new asn1js.Sequence({
                name: "SignerInfo",
                value: [
                    new asn1js.Integer({ name: (names.version || "SignerInfo.version") }),
                    new asn1js.Choice({
                        value: [
                            IssuerAndSerialNumber.schema(names.sid || {
                                names: {
                                    blockName: "SignerInfo.sid"
                                }
                            }),
-                           new asn1js.Constructed({
+                           new asn1js.Primitive({
                                optional: true,
                                name: (names.sid || "SignerInfo.sid"),
                                idBlock: {
                                    tagClass: 3, // CONTEXT-SPECIFIC
                                    tagNumber: 0 // [0]
                                },
                                value: [new asn1js.OctetString()]
                            }),
                        ]
                    }),
microshine commented 2 years ago

@ties I published pkijs@2.1.96

Please try it

ties commented 2 years ago

@microshine sorry for the delay. I can confirm that pkijs as well as asn1-js can parse the CMS and that I succeeded with parsing the content with the latter.