modules/cryptApi.js

/**
 * FFI wrapper over Windows CryptoApi (crypt32.dll)
 * See `\Samples\UpdateCertificateList\updateCRL.js` for usage example.
 *
 * @module cryptApi
 * @author xmax
 */

//TODO - rewrite. remove keyword const! remove function cryptApi. Use module.expors to expose API
var ffi = require('ffi');
var winApi = require('winApi');

var cryptApi = {};

var CRYPT32_DLL = 'crypt32.dll';
var cst;

//***********  Const ************
cryptApi.const = cst = {
  CERT_QUERY_OBJECT_FILE: 0x00000001,
  CERT_QUERY_OBJECT_BLOB: 0x00000002,

//encoded single certificate
  CERT_QUERY_CONTENT_CERT:  1,
//encoded single CTL
CERT_QUERY_CONTENT_CTL: 2,
//encoded single CRL
CERT_QUERY_CONTENT_CRL: 3,
//serialized store
CERT_QUERY_CONTENT_SERIALIZED_STORE: 4,
//serialized single certificate
CERT_QUERY_CONTENT_SERIALIZED_CERT: 5,
//serialized single CTL
CERT_QUERY_CONTENT_SERIALIZED_CTL: 6,
//serialized single CRL
CERT_QUERY_CONTENT_SERIALIZED_CRL: 7,
//a PKCS#7 signed message
CERT_QUERY_CONTENT_PKCS7_SIGNED: 8,
//a PKCS#7 message, such as enveloped message.  But it is not a signed message,
CERT_QUERY_CONTENT_PKCS7_UNSIGNED: 9,
//a PKCS7 signed message embedded in a file
CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED: 10,
//an encoded PKCS#10
CERT_QUERY_CONTENT_PKCS10: 11,
//an encoded PKX BLOB
CERT_QUERY_CONTENT_PFX: 12,
//-------------------------------------------------------------------------
//dwFormatType for CryptQueryObject
//-------------------------------------------------------------------------
//the content is in binary format
 CERT_QUERY_FORMAT_BINARY: 1,
//the content is base64 encoded
 CERT_QUERY_FORMAT_BASE64_ENCODED: 2,

 CERT_SIMPLE_NAME_STR: 1,
 CERT_OID_NAME_STR: 2,
 CERT_X500_NAME_STR: 3,

//+-------------------------------------------------------------------------
//  Certificate name string type flags OR'ed with the above types
//--------------------------------------------------------------------------
 CERT_NAME_STR_SEMICOLON_FLAG: 0x40000000,
 CERT_NAME_STR_NO_PLUS_FLAG: 0x20000000,
 CERT_NAME_STR_NO_QUOTING_FLAG: 0x10000000,
 CERT_NAME_STR_CRLF_FLAG: 0x08000000,
 CERT_NAME_STR_COMMA_FLAG: 0x04000000
};



/*jshint bitwise: false*/
//-------------------------------------------------------------------------
//dwExpectedConentTypeFlags for CryptQueryObject
//-------------------------------------------------------------------------
//encoded single certificate
cst.CERT_QUERY_CONTENT_FLAG_CERT = 1 << cst.CERT_QUERY_CONTENT_CERT;
//encoded single CTL
cst.CERT_QUERY_CONTENT_FLAG_CTL = 1 << cst.CERT_QUERY_CONTENT_CTL;
//encoded single CRL
cst.CERT_QUERY_CONTENT_FLAG_CRL = 1 << cst.CERT_QUERY_CONTENT_CRL;
//serialized store
cst.CERT_QUERY_CONTENT_FLAG_SERIALIZED_STORE = 1 << cst.CERT_QUERY_CONTENT_SERIALIZED_STORE;
//serialized single certificate
cst.CERT_QUERY_CONTENT_FLAG_SERIALIZED_CERT = 1 << cst.CERT_QUERY_CONTENT_SERIALIZED_CERT;
//serialized single CTL
cst.CERT_QUERY_CONTENT_FLAG_SERIALIZED_CTL = 1 << cst.CERT_QUERY_CONTENT_SERIALIZED_CTL;
//serialized single CRL
cst.CERT_QUERY_CONTENT_FLAG_SERIALIZED_CRL = 1 << cst.CERT_QUERY_CONTENT_SERIALIZED_CRL;
//an encoded PKCS#7 signed message
cst.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED = 1 << cst.CERT_QUERY_CONTENT_PKCS7_SIGNED;
//an encoded PKCS#7 message.  But it is not a signed message
cst.CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED = 1 << cst.CERT_QUERY_CONTENT_PKCS7_UNSIGNED;
//the content includes an embedded PKCS7 signed message
cst.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED = 1 << cst.CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED;
//an encoded PKCS#10
cst.CERT_QUERY_CONTENT_FLAG_PKCS10 = 1 << cst.CERT_QUERY_CONTENT_PKCS10;
//an encoded PFX BLOB
cst.CERT_QUERY_CONTENT_FLAG_PFX = 1 << cst.CERT_QUERY_CONTENT_PFX;

//content can be any type
cst.CERT_QUERY_CONTENT_FLAG_ALL = cst.CERT_QUERY_CONTENT_FLAG_CERT |
cst.CERT_QUERY_CONTENT_FLAG_CTL |
cst.CERT_QUERY_CONTENT_FLAG_CRL |
cst.CERT_QUERY_CONTENT_FLAG_SERIALIZED_STORE |
cst.CERT_QUERY_CONTENT_FLAG_SERIALIZED_CERT |
cst.CERT_QUERY_CONTENT_FLAG_SERIALIZED_CTL |
cst.CERT_QUERY_CONTENT_FLAG_SERIALIZED_CRL |
cst.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED |
cst.CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED |
cst.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED |
cst.CERT_QUERY_CONTENT_FLAG_PKCS10 |
cst.CERT_QUERY_CONTENT_FLAG_PFX;


//-------------------------------------------------------------------------
//dwExpectedFormatTypeFlags for CryptQueryObject
//-------------------------------------------------------------------------
//the content is in binary format
cst.CERT_QUERY_FORMAT_FLAG_BINARY = 1 << cst.CERT_QUERY_FORMAT_BINARY;
//the content is base64 encoded
cst.CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED = 1 << cst.CERT_QUERY_FORMAT_BASE64_ENCODED;
//the content can be of any format
cst.CERT_QUERY_FORMAT_FLAG_ALL = cst.CERT_QUERY_FORMAT_FLAG_BINARY | cst.CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED;

/*jshint bitwise: true*/

//*********** TYPE **************
var refT = ffi.types.refType,
    DWORD = ffi.types.uint32,
    PBYTE = ffi.types.puint8,
    LPSTR = ffi.types.ansiString,
    BOOL = ffi.types.uint8,
    Pointer = new refT(ffi.types.void),
    PPointer = new refT(Pointer),
    PVOID = new refT(ffi.types.void),
    PDWORD = new refT(DWORD),
    PWideString = new refT(ffi.types.wideString),
    LPDWORD = PDWORD;

cryptApi.types = {
    DWORD: DWORD,
    PBYTE: PBYTE,
    LPSTR: LPSTR,
    BOOL: BOOL,
    Pointer: Pointer,
    PPointer: PPointer,
    PVOID: PVOID,
    PWideString: PWideString,
    LPDWORD: LPDWORD
};

// structure definition
var CRYPTOAPI_BLOB = ffi.Struct({
  cbData : DWORD,
  pbData : PBYTE
}),
CRYPT_OBJID_BLOB = CRYPTOAPI_BLOB,
CERT_NAME_BLOB = CRYPTOAPI_BLOB,
PCERT_NAME_BLOB = new refT(CERT_NAME_BLOB),
CRYPT_INTEGER_BLOB = CRYPTOAPI_BLOB;

var HCERTSTORE = PVOID,
    HCRYPTMSG = Pointer,
CRYPT_ALGORITHM_IDENTIFIER = ffi.Struct({
  pszObjId   : LPSTR,
  Parameters : CRYPT_OBJID_BLOB
});

var FILETIME = ffi.Struct({
  dwLowDateTime: DWORD,
  dwHighDateTime: DWORD
});


var CERT_EXTENSION = ffi.Struct({
    pszObjId :LPSTR,
    fCritical :BOOL,
    Value :CRYPT_OBJID_BLOB
}),
PCERT_EXTENSION = new refT(CERT_EXTENSION);


var CRL_ENTRY = ffi.Struct({
    SerialNumber: CRYPT_INTEGER_BLOB,
    RevocationDate: FILETIME, //TFILETIME;
    cExtension: DWORD,
    rgExtension : PCERT_EXTENSION
}),
PCRL_ENTRY = new refT(CRL_ENTRY);


var CRL_INFO = ffi.Struct({
    dwVersion           : ffi.types.uint32, //DWORD;
    SignatureAlgorithm  : CRYPT_ALGORITHM_IDENTIFIER,
    Issuer              : CERT_NAME_BLOB,
    ThisUpdate          : FILETIME,
    NextUpdate          : FILETIME,
    cCRLEntry           : DWORD,
    rgCRLEntry          : PCRL_ENTRY,
    cExtension          : DWORD,
    rgExtension         : PCERT_EXTENSION
}),
PCRL_INFO = new refT(CRL_INFO);


var CRL_CONTEXT = ffi.Struct({
    dwCertEncodingType: ffi.types.uint32, //DWORD
    pbCrlEncoded: ffi.types.puint8, // PBYTE,
    cbCrlEncoded: ffi.types.uint32, // DWORD
    pCrlInfo: PCRL_INFO,
    hCertStore :HCERTSTORE
}),
PCCRL_CONTEXT = new refT(CRL_CONTEXT),
PPCCRL_CONTEXT = new refT(PCCRL_CONTEXT);

cryptApi.struct = {
    CRYPT_ALGORITHM_IDENTIFIER: CRYPT_ALGORITHM_IDENTIFIER,
    FILETIME: FILETIME,
    CERT_EXTENSION: CERT_EXTENSION,
    PCERT_EXTENSION: PCERT_EXTENSION,
    CRL_ENTRY: CRL_ENTRY,
    PCRL_ENTRY: PCRL_ENTRY,
    CRL_INFO: CRL_INFO,
    PCRL_INFO: PCRL_INFO,
    CRL_CONTEXT: CRL_CONTEXT,
    PCCRL_CONTEXT: PCCRL_CONTEXT,
    PPCCRL_CONTEXT: PPCCRL_CONTEXT,
    CRYPTOAPI_BLOB: CRYPTOAPI_BLOB,
    CRYPT_OBJID_BLOB: CRYPT_OBJID_BLOB,
    CERT_NAME_BLOB: CERT_NAME_BLOB,
    PCERT_NAME_BLOB: PCERT_NAME_BLOB,
    CRYPT_INTEGER_BLOB: CRYPT_INTEGER_BLOB
};

/*
 function CertNameToStrA(dwCertEncodingType :DWORD;
 pName :PCERT_NAME_BLOB;
 dwStrType :DWORD;
 psz :LPSTR; //OPTIONAL
 csz :DWORD):DWORD ; stdcall;
 //+-------------------------------------------------------------------------
 //--------------------------------------------------------------------------
 function CertNameToStrW(dwCertEncodingType :DWORD;
 pName :PCERT_NAME_BLOB;
 dwStrType :DWORD;
 psz :LPWSTR; //OPTIONAL
 csz :DWORD):DWORD ; stdcall;
 */
//function CertFreeCRLContext(pCrlContext :PCCRL_CONTEXT):BOOL
cryptApi.crypt32 = crypt32 = ffi.Library(CRYPT32_DLL, {
    CertNameToStrA: {
        rval: DWORD,
        args: [
            DWORD, //dwCertEncodingType
            PCERT_NAME_BLOB, //pName :
            DWORD, //dwStrType
            LPSTR, // psz OPTIONAL
            DWORD //csz
        ]
    },
    CryptQueryObject: {
          rval: BOOL,
          args: [
            DWORD, //dwObjectType:
            Pointer, //pvObject:
            DWORD,  //dwExpectedContentTypeFlags,
            DWORD, //dwExpectedFormatTypeFlags,
            DWORD, //dwFlags: ;
            LPDWORD,  //pdwMsgAndCertEncodingType,
            LPDWORD, //pdwContentType,
            LPDWORD, // pdwFormatType;
            HCERTSTORE, //phCertStore
            HCRYPTMSG, //phMsg
            PPointer //ppvContext
          ]
    },
    CertFreeCRLContext: {
        rval: BOOL,
        args: [
            PCCRL_CONTEXT  //pCrlContext
        ]
    }
});

/**
 * Parse CRL (certificate revocation list) file.
 * Result object has structure `{issuer: String, crl:{[{serial: [], revocationDate: Date }]}}`
 * @param {String} fileName  CRL file name
 * @returns {Object}
 */
module.exports.getCRLInfo = function getCRLInfo(fileName){
    var s = new ffi.types.wideString(),
        result = { crl:[] },
        i, res, ppvContext, countCert, rgCRLEntry, vContext,
        sn, byteSN, serialPC, rvDate, bDat, Issuer, IssuerB,
        rgCRLEntryRef, issuerStr = new LPSTR(); //ffi.types.ansiString()
    s.setStr(fileName);

    ppvContext = new PPCCRL_CONTEXT(1);
    //ppvContext.alloc();
    ppvContext.set( new PCCRL_CONTEXT(1) );

    //D:\temp\ACSKIDD-Delta.crl

    res = crypt32.CryptQueryObject(
        cst.CERT_QUERY_OBJECT_FILE,  //DWORD dwObjectType
        s, //pvObject:
        cst.CERT_QUERY_CONTENT_FLAG_CRL, //dwExpectedContentTypeFlags,
        cst.CERT_QUERY_FORMAT_FLAG_BINARY, //dwExpectedFormatTypeFlags
        0, //dwFlags: ;
        winApi.nil,  //pdwMsgAndCertEncodingType,
        winApi.nil, //pdwContentType,
        winApi.nil, // pdwFormatType;
        winApi.nil, //phCertStore
        winApi.nil, //phMsg
        ppvContext //ppvContext
    );
    if (!res){
         winApi.riseLastWinError();
    }
    vContext = ppvContext.get().get();
    Issuer = vContext.pCrlInfo.get().Issuer;
    IssuerB = new PCERT_NAME_BLOB(1);
    IssuerB.set(Issuer);
    res = crypt32.CertNameToStrA(
        vContext.dwCertEncodingType,
        IssuerB,
        cst.CERT_X500_NAME_STR,
        winApi.nil,
        0
    );
    issuerStr.alloc(res);
    res = crypt32.CertNameToStrA(
        vContext.dwCertEncodingType,
        IssuerB,
        cst.CERT_X500_NAME_STR,
        issuerStr,
        res
    );

    result.issuer = issuerStr.getStr();
    issuerStr.free();
    IssuerB.free();
    result.issuer = result.issuer.replace( "\u0000", "");
    countCert = vContext.pCrlInfo.get().cCRLEntry || 0;
    rgCRLEntryRef = vContext.pCrlInfo.get().rgCRLEntry;
    for ( i = 0; i < countCert; i++ ){
        rgCRLEntry = rgCRLEntryRef.get(i);
        /*
        if ( (i / 1000) === Math.round(i / 1000)  ){
            toLog(i);
            if (console){
                console.debug(i);
            }
        }
        */
        sn = rgCRLEntry.SerialNumber;
        serialPC = [];
        if (sn.pbData.address() <= 0){
            continue;
        }
        for(var y = sn.cbData - 1; y >= 0 ; y--){
            byteSN = sn.pbData.get(y);
            /*jshint bitwise: false*/
            serialPC.push(
                (byteSN >> 4).toString(16),
                (byteSN & 0xF).toString(16)
            );
            /*jshint bitwise: true*/
        }
        rvDate = winApi.fileTimeToDate(rgCRLEntry.RevocationDate);
        result.crl.push({serial: serialPC, revocationDate: rvDate });
    }

    res = crypt32.CertFreeCRLContext( ppvContext.get() );
    if (!res){
        winApi.riseLastWinError();
    }
    ppvContext.get().free();
    ppvContext.free();

    return result;
};