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

How to use SubjectKeyIdentifier as the RecipientIdentifier of EnvelopedData #261

Closed xybei closed 4 years ago

xybei commented 4 years ago

Thanks to the author for creating the PKI.js and ASN1.js projects, they helped me a lot!

I want to use the SubjectKeyIdentifier as the RecipientIdentifier of EnvelopedData,and I reimplemented addRecipientByCertificate() in my own subclass of EnvelopedData: `

            const subjectKeyIdentifier = certificate.getSubjectKeyIdentifier();
            if (!subjectKeyIdentifier) {
                Log.throw('The Certificate has no \'SubjectKeyIdentifier\' field!');
            }

            const keyInfo = new pkijs.KeyTransRecipientInfo({
                version: 2,
                rid: new asn1js.Primitive({
                    idBlock: {
                        tagClass: 3, // CONTEXT-SPECIFIC
                        tagNumber: 0, // [0]
                    },
                    valueHex: subjectKeyIdentifier, // ArrayBuffer of 20 bytes subjectKeyIdentifier
                }),
                keyEncryptionAlgorithm: new pkijs.AlgorithmIdentifier({
                    algorithmId: keyEncryptionAlgOID,
                    algorithmParams: new asn1js.Null(),
                }),
                recipientCertificate: certificate,
                // "encryptedKey" will be calculated in "encrypt" function
            });

            this.recipientInfos.push(new pkijs.RecipientInfo({
                variant: 1,
                value: keyInfo,
            }));

`

I found that the EnvelopedData generated in this way has two layers of Context[0] upon subjectKeyIdentifier: MIH8BgkqhkiG9w0BBwOgge4wgesCAQIxgbAwga0CAQKgFoAUA2BCLzwNEVyoA3e9WHxfttS1cqAwDQYJKoZIhvcNAQEBBQAEgYAfdAI9VT7f+0ZaNlsY5lYs74atJQ9gXwqWyF3Y1fu+SlVRnOe1nAKvKxszG+SKAkroCXE6COa+Ga5rhOQEUaKB24iLfF3p0ubncxpDjLlqDxvnsqyWd64M8xQ5XcGc3waq1N1AFr7ytMpsU3MLwh6GfPyIgYkANUky1RzcjpIzRzAzBgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECIB0EjTuxzL5gBCYskm+QX/P7n+4phCIqSDe

I have compared with OpenSSL and Windows API, there are only one layer of Context[0] upon subjectKeyIdentifier: MIH6BgkqhkiG9w0BBwOggewwgekCAQIxga4wgasCAQKAFDh4FX8892+xoylOINJzM3X+n+XeMA0GCSqGSIb3DQEBAQUABIGAPBdx9JtxfmfBUgz06iWpBlW0W0PfCWZILl9Vr9WMWN21stdhdCfOq7WLqHelGusjadxRcfbTtEqY50BhoTeJd15l96HS6JfJi15iihPxq+tDvdmcqJzFkhGuGmXDh73WuxrOG63SmshJMzliD+qdxiafEQbBPAk1E28v7dpSA/0wMwYJKoZIhvcNAQcBMBQGCCqGSIb3DQMHBAikwYGcNIXftIAQ7u9jyWdK/CrRfeo+S/aJLw==

By debugging the code, I found that the KeyTransRecipientInfo and RecipientIdentifier classes need to be modified to solve this problem. I didn‘t find a simple and reasonable way to solve this problem just by adjusting my own code.

Could you give me some help? Thanks!

YuryStrozhevsky commented 4 years ago

@xybei Have you been trying to decrypt Enveloped Data made by PKIJS in OpenSSL? Have you seen any mistakes there? Personally I have not seen any.

xybei commented 4 years ago

@YuryStrozhevsky Yes, I have tested it, the details are as follows:

OpenSSL: Either one or two layers of Context[0] can be decrypted by OpenSSL. Windows API: CryptDecryptMessage() will report an error("ASN1 bad tag value met.", 0x8009310B) if there are two layers of Context[0].

If I specify to use SubjectKeyIdentifier as the RecipientIdentifier, the EnvelopedData generated by Windows API and OpenSSL have only one layer of Context[0]. And the EnvelopedData in this format cannot be decoded by PKI.js.

I used the current latest version of openssl-1.1.1d. https://github.com/openssl/openssl/blob/master/demos/cms/cms_enc.c

You did not encounter this problem, it may be because the PKI.js top-level API uses IssuerAndSerialNumber as the RecipientIdentifier. I saw that the underlying implementation of PKI.js can support the use of SubjectKeyIdentifier, and I used it in my subclass code and encountered this problem.

https://tools.ietf.org/html/rfc5652#section-6.2.1 `

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

   SubjectKeyIdentifier ::= OCTET STRING

`

From the definition of OpenSSL, it seems that there is only one layer of Context[0]. https://github.com/openssl/openssl/blob/master/crypto/cms/cms_local.h#L97

`

  typedef CMS_SignerIdentifier CMS_RecipientIdentifier;

  struct CMS_SignerIdentifier_st {
      int type;
      union {
          CMS_IssuerAndSerialNumber *issuerAndSerialNumber;
          ASN1_OCTET_STRING *subjectKeyIdentifier;
      } d;
  };

`

YuryStrozhevsky commented 4 years ago

@xybei Thanks, you found a real bug. But your solution was not 100% correct. I made a new one and released a new version of PKIjs.

xybei commented 4 years ago

@YuryStrozhevsky Thank you! I tested the latest code, and got an error “Cannot read property 'slice' of undefined” at: https://github.com/PeculiarVentures/ASN1.js/blob/master/src/asn1.js#L1015

I think the following line needs to be changed to valueHex: this.rid.valueBlock.valueHex https://github.com/PeculiarVentures/PKI.js/blob/36ec54a70e5f824e2a994d6dcbee1bedf53b77cb/src/KeyTransRecipientInfo.js#L218

This is the code in my subclass, I wonder if it is correct? `

const subjectKeyIdentifier = certificate.getSubjectKeyIdentifier();
if (!subjectKeyIdentifier) {
    Log.throw('The Certificate has no \'SubjectKeyIdentifier\' field!');
}

const keyInfo = new pkijs.KeyTransRecipientInfo({
    version: 2,
    rid: new asn1js.Primitive({
        idBlock: {
            tagClass: 3, // CONTEXT-SPECIFIC
            tagNumber: 0, // [0]
        },
        valueHex: subjectKeyIdentifier, // ArrayBuffer of 20 bytes subjectKeyIdentifier
    }),
    keyEncryptionAlgorithm: new pkijs.AlgorithmIdentifier({
        algorithmId: keyEncryptionAlgOID,
        algorithmParams: new asn1js.Null(),
    }),
    recipientCertificate: certificate,
    // "encryptedKey" will be calculated in "encrypt" function
});

this.recipientInfos.push(new pkijs.RecipientInfo({
    variant: 1,
    value: keyInfo,
}));

`

YuryStrozhevsky commented 4 years ago

@xybei Yes, I did not test encoding routine. Fixed.

xybei commented 4 years ago

Works well, Thanks!