Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AsnConvert an array to a Set #50

Open
dhensby opened this issue May 11, 2021 · 6 comments
Open

AsnConvert an array to a Set #50

dhensby opened this issue May 11, 2021 · 6 comments
Assignees

Comments

@dhensby
Copy link
Contributor

dhensby commented May 11, 2021

I need to convert an array of Attributes to an encoded Set, however passing a raw array to AsnConvert results in Error: Cannot get schema for 'Array' target (unsurprisingly).

example code:

const { AsnConvert } = require('@peculiar/asn1-schema');
const { Attribtue } = require('@peculiar/asn1-cms');
const { id_pkcs9_at_messageDigest } = require('@peculiar/asn1-pkcs9');

const attributes = [new Attribute({
  attrType: id_pkcs9_at_messageDigest,
  attrValues: [Buffer.from(...)],
});

const encoded = AsnConvert.serialize(attributes); // errors
@dhensby
Copy link
Contributor Author

dhensby commented May 12, 2021

This is specifically a problem for what I'm trying to do which is sign the signedAttrs of SignerInfo. To sign them the attribute needs to be encoded as a set, but the SignedAttributes is just a type (export declare type SignedAttributes = Attribute[]) and not an actual constructor. If this were defined as a class, I think it would solve the problem, allowing the attributes to be encoded.

I would suspect this could be a problem in any area where there's no canonical definition of a type and instead it's just declared as a AsnProp on a class but can't be encoded in a standalone way. Maybe it would be cool if you could do something like AsnConvert.serialize(attributes, SigerInfo.signedAttrs).

Another example would be encoding OIDs (AsnConvert.serialize(oid) - results in Error: Cannot get schema for 'String' target)

@microshine
Copy link
Contributor

const signedData = new SignedData();
signedData.signerInfos.push(new SignerInfo({
  signedAttrs: [
    new Attribute({
      attrType: "1.2.3.4.5.6",
      attrValues: [
        new Uint8Array([2, 1, 3]).buffer,
      ]
    })
  ]
}));

const raw = AsnConvert.serialize(signedData);
console.log(Convert.ToHex(raw));

Output

30300201003100300206003125302302010030043000020030020600a00e300c06052a030405063103020103300206000400

ASN.1

SEQUENCE (4 elem)
  INTEGER 0
  SET (0 elem)
  SEQUENCE (1 elem)
    OBJECT IDENTIFIER
  SET (1 elem)
    SEQUENCE (6 elem)
      INTEGER 0
      SEQUENCE (2 elem)
        SEQUENCE (0 elem)
        INTEGER 0
      SEQUENCE (1 elem)
        OBJECT IDENTIFIER
      [0] (1 elem)
        SEQUENCE (2 elem)
          OBJECT IDENTIFIER 1.2.3.4.5.6
          SET (1 elem)
            INTEGER 3
      SEQUENCE (1 elem)
        OBJECT IDENTIFIER
      OCTET STRING (0 byte)

@microshine
Copy link
Contributor

Method serialize doesn't support simple times (eg number, string, boolean, array, etc). I think it would be better to add fromJSON method to AsnConvert class for such cases.

AsnConvert.fromJSON(1); // INTEGER 1
AsnConvert.fromJSON("Hello"); // PrintableString Hello
AsnConvert.fromJSON([ 1, 2, 3]); // SET INTEGER 1 INTEGER 2 INTEGER 3

@dhensby What do you think about it?

@dhensby
Copy link
Contributor Author

dhensby commented May 13, 2021

@microshine - from your first reply, that encodes the entire SignedData object, but for the signed data we need to encode the signedAttrs prop (as a SET) and sign that. Which can't be done as it stands.

In terms of a fromJSON prop, possibly, but what if we need to force the time, for example: AsnConver.fromJSON([1, 2, 3]) could be a SET or a SEQUENCE (right?) and likewise, some could be implicit, have context values, etc.

I feel like the "right" response would be that the props weren't assigned their encoding rules only by the annotation, but also by having content types that can be encoded standalone.

At the moment SignedAttributes is just a type defined as an array of Attributes. But if this was a class which was annotated as a SET with itemTypes, this could be encoded standalone.

Perhaps even having a standalone "basic" type of SET would work too (which, in fact I've done as a little polyfill for now):

@AsnType({ type: AsnTypeTypes.Set, itemType: AsnPropTypes.Any })
export class Set<T> extends AsnArray<T> {
}

@andsens
Copy link

andsens commented Feb 12, 2024

Here's a workaround to get the data that should be signed (the [3] is because the third attribute in my CMS is the message digest, adjust accordingly):

const toBeSigned = Buffer.from(
	(asn1js.fromBER(asn1Schema.AsnSerializer.serialize(signerInfo)).result.valueBlock as any).value[3]
		.valueBeforeDecode,
	'hex',
);
toBeSigned[0] = 0x31; // See https://datatracker.ietf.org/doc/html/rfc5652#section-5.4

@dhensby
Copy link
Contributor Author

dhensby commented Feb 12, 2024

I'm just doing this at the moment to get around it:

const {
    AsnType,
    AsnArray,
    AsnTypeTypes,
    AsnPropTypes,
} = require('@peculiar/asn1-schema');

class Set extends AsnArray {}

// this is a little hack to allow us to define an Asn1 type `Set` which acts as an array
AsnType({ type: AsnTypeTypes.Set, itemType: AsnPropTypes.Any })(Set);

module.exports = { Set };

Then I can encode the Set (truncated example):

            // signed attributes must be an ordered set
            const signedAttrs = new Set(authenticatedAttributes.map(({ type, value }) => {
                return AsnConvert.serialize(new Attribute({
                    attrType: type,
                    attrValues: [AsnConvert.serialize(value)],
                }));
            }).sort((a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b))));
            // encode the Set for signing
            const encodedAttrs = AsnConvert.serialize(signedAttrs);
            // perform your signing somehow
            const signature = await signer(Buffer.from(encodedAttrs));
            // construct the signer info for use in SignedData
            return new SignerInfo({
                version: CMSVersion.v1,
                sid: new SignerIdentifier({
                    issuerAndSerialNumber: new IssuerAndSerialNumber({
                        issuer: certificate.tbsCertificate.issuer,
                        serialNumber: certificate.tbsCertificate.serialNumber,
                    }),
                }),
                digestAlgorithm: new DigestAlgorithmIdentifier({
                    algorithm: digestAlgOid,
                    parameters: null,
                }),
                // it would be nice to re-use the `signedAttrs` from above, but I'm not sure that's possible
                signedAttrs: authenticatedAttributes.map(({ type, value }) => {
                    return new Attribute({
                        attrType: type,
                        attrValues: [AsnConvert.serialize(value)],
                    });
                }).sort((a, b) => Buffer.compare(Buffer.from(AsnConvert.serialize(a)), Buffer.from(AsnConvert.serialize(b)))),
                signatureAlgorithm: new SignatureAlgorithmIdentifier({
                    algorithm: id_rsaEncryption,
                    parameters: null,
                }),
                signature: new OctetString(signature),
            });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants