Question

I'm trying to create signed PKCS#7 message for PKCS#10 certifacate request on client-side with javascript.

There are good examples on PKCS#10: http://blogs.msdn.com/b/alejacma/archive/2009/01/28/how-to-create-a-certificate-request-with-certenroll-javascript.aspx

But I need to create PKCS#7 and cannot figure out how to do it. There is a lack of examples (actually no at all) on official documentation for CertEnroll: http://msdn.microsoft.com/en-us/library/windows/desktop/aa374850(v=vs.85).aspx

I've ended up with this code:

var XCN_CRYPT_STRING_BASE64REQUESTHEADER = 3;

var XCN_CERT_NAME_STR_NONE = 0;

var _certEnrollClassFactory = new ActiveXObject("X509Enrollment.CX509EnrollmentWebClassFactory");


ComposePKCS10Request: function (containerName, subject)
{
    // PKCS #10 certificate request
    var objRequest = _certEnrollClassFactory.CreateObject("X509Enrollment.CX509CertificateRequestPkcs10");

    var objCSP = objCertEnrollClassFactory.CreateObject("X509Enrollment.CCspInformation");
    var objCSPs = objCertEnrollClassFactory.CreateObject("X509Enrollment.CCspInformations");

    //  Initialize the csp object using the desired Cryptograhic Service Provider (CSP)
    objCSP.InitializeFromName("Microsoft Enhanced Cryptographic Provider v1.0");

    //  Add this CSP object to the CSP collection object
    objCSPs.Add(objCSP);

    // asymmetric private key that can be used for encryption, signing, and key agreement.
    var objPrivateKey = _certEnrollClassFactory.CreateObject("X509Enrollment.CX509PrivateKey");

    //  Provide key container name, key length and key spec to the private key object
    objPrivateKey.ContainerName = containerName;
    //objPrivateKey.Length = 1024;
    objPrivateKey.KeySpec = 1; // AT_KEYEXCHANGE = 1

    //  Provide the CSP collection object (in this case containing only 1 CSP object)
    //  to the private key object
    objPrivateKey.CspInformations = objCSPs;

    // Initialize P10 based on private key
    objRequest.InitializeFromPrivateKey(1, objPrivateKey, ""); // context user = 1

    // X.500 distinguished name (DN)
    // The DN consists of a sequence of relative distinguished names (RDNs). Each RDN consists of a set of attributes, 
    // and each attribute consists of an object identifier (OID) and a value. The data type of the value is identified 
    // by the DirectoryString structure.
    var objDn = _certEnrollClassFactory.CreateObject("X509Enrollment.CX500DistinguishedName");

    // DN related stuff
    objDn.Encode(subject, XCN_CERT_NAME_STR_NONE);
    objRequest.Subject = objDn;

    return objRequest;
}

CreatePKCS7: function (containerName, subject)
{
    // PKCS #7 certificate request
    var objPKCS7Request = _certEnrollClassFactory.CreateObject("X509Enrollment.CX509CertificateRequestPkcs7");

    // initialize PKCS #7 certificate request by PKCS #10 certificate request
    objPKCS7Request.InitializeFromInnerRequest(this.ComposePKCS10Request(containerName, subject));

    var objSignerCert = _certEnrollClassFactory.CreateObject("X509Enrollment.CSignerCertificate");
    var verifyType = 4; /* VerifyAllowUI, see typedef enum X509PrivateKeyVerify */
    var encodingType = 0x3; /* see typedef enum EncodingType */

    /**********************************************************************/
    /* I have to provide certificate here??? How can I obtain it from UI? */
    /**********************************************************************/
    var strCertificate = '?????????????????????';

    objSignerCert.Initialize(false, verifyType, encodingType, strCertificate);

    /*****************************************************************************/
    /* Also I'm not shure that SignerCertificate can be accessed via javascript. */
    /*****************************************************************************/
    objPKCS7Request.SignerCertificate = objSignerCert;

    // represents the top level object and enables you to enroll in a certificate hierarchy and install a certificate response
    var objEnroll = _certEnrollClassFactory.CreateObject("X509Enrollment.CX509Enrollment");

    // Enroll
    objEnroll.InitializeFromRequest(objPKCS7Request);

    var pkcs7;

    try
    {
        pkcs7 = objEnroll.CreateRequest(XCN_CRYPT_STRING_BASE64REQUESTHEADER);
    }
    catch (e)
    {
        ...
    }

    return pkcs7;
}

Is there any way to create PKCS#7 message with javascript?

UPDATE: I've already had PKCS#10 cert request (see the first function in code sample) and need to create PKCS#7 signed message for it. Ok, I paraphrase my question. How to create signed PKCS#7 message with javascript? (Ideally, it should allow to specify proper cert with UI.)

As for javascript I understand that it's not the convenient way, but suitable because I must to deal with it on client-side (in browser). Moreover, cert enroll IX509CertificateRequestPkcs7 interface has methods marked as [WebEnabled], so I believe there must be the way to do what I state.

Was it helpful?

Solution 3

VBScript method (it will prompt to choose certificate):

Function SignMessage(Message)
    Dim oUtils
    Set oUtils = CreateObject("CAPICOM.Utilities")
    Dim cpcSigner
    Set cpcSigner = CreateObject("CAPICOM.Signer")
    cpcSigner.Options = 2 'CAPICOM_CERTIFICATE_INCLUDE_END_ENTITY_ONLY
    Dim cpcSignedData
    Set cpcSignedData = CreateObject("CAPICOM.SignedData")
    cpcSignedData.Content = oUtils.Base64Decode(Message)
    SignMessage = cpcSignedData.Sign(cpcSigner, False)
End function

Javascript method (be carefull due to encoding strings in JS you will get corrupted signature, so I suggest to use VBScript for signing data in your client scripts since it interoperable with JS):

var CAPICOM_CERTIFICATE_INCLUDE_END_ENTITY_ONLY = 2;

SignMessage: function (message) {
    var cpcSigner = new ActiveXObject("CAPICOM.Signer");
    cpcSigner.Options = CAPICOM_CERTIFICATE_INCLUDE_END_ENTITY_ONLY;

    var cpcSignedData = new ActiveXObject("CAPICOM.SignedData");
    var oUtils = new ActiveXObject("CAPICOM.Utilities");
    cpcSignedData.Content = oUtils.Base64Decode(message);

    return cpcSignedData.Sign(cpcSigner, false);
}

OTHER TIPS

You can do PKCS#7 and PKCS#10 in pure JS using Forge (works in browser or node.js):

https://github.com/digitalbazaar/forge#pkcs7

https://github.com/digitalbazaar/forge#pkcs10

PKCS#7 format is defined in rfc2315 using ASN.1 notation.

ASN.1

ASN.1 is a notation for defining data structures. Additionaly, there is also DER - encoding rules that define how ASN.1 notation is encoded as binary data.

ASN1.js encoder

So the question boils down to "are there any JS-libraries that implements ASN1?" There are some decoding libraries

But the only encoder is this one: https://github.com/indutny/asn1.js

ContentInfo and types

Let's dive deeper. The ASN.1 for PKCS#7 follows:

ContentInfo ::= SEQUENCE {
  contentType ContentType,
  content
    [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }

ContentType ::= OBJECT IDENTIFIER

Here we need to understand what is OBJECT IDENTIFIER. It is a special sequence of numbers that defines some type. Valid object identifiers for PKCS#7 follow:

data OBJECT IDENTIFIER ::= { pkcs-7 1 }
signedData OBJECT IDENTIFIER ::= { pkcs-7 2 }
envelopedData OBJECT IDENTIFIER ::= { pkcs-7 3 }
signedAndEnvelopedData OBJECT IDENTIFIER ::= { pkcs-7 4 }
digestedData OBJECT IDENTIFIER ::= { pkcs-7 5 }
encryptedData OBJECT IDENTIFIER ::= { pkcs-7 6 }

pkcs-7 is an object identifier for PKCS#7 format.

pkcs-7 OBJECT IDENTIFIER ::=
  { iso(1) member-body(2) US(840) rsadsi(113549)
      pkcs(1) 7 }

As you can see, there's ANY DEFINED BY contentType modifier for the content field. It means that this field can be any of 6 types depending on the value of contentType field.

It seems like you need signed message, so the content type would be "signedData"

ContentInfo encoding using JS

How this translates into ASN1.js code? Here you're:

var PKCS7_CONTENT_TYPES = {
    "1 2 840 113549 1 7 1": "data",
    "1 2 840 113549 1 7 2": "signedData",
    "1 2 840 113549 1 7 3": "envelopedData",
    "1 2 840 113549 1 7 4": "signedAndEnvelopedData",
    "1 2 840 113549 1 7 5": "digestData",
    "1 2 840 113549 1 7 6": "encryptedData",
};

var ContentInfo = asn1.define('ContentInfo', function() {
    this.seq().obj(
        this.key('contentType').objid(PKCS7_CONTENT_TYPES),
        this.key('content').optional().explicit(0).any()
    );
});

So, how to use this ContentInfo?

var signedData = ...
   contentInfoEncoded = ContentInfo.encode({
     'contentType': 'signedData',
     'content': signedData
   })

SignedData

To be continued...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top