PeculiarVentures / graphene

A simple layer for interacting with PKCS #11 / PKCS11 / CryptoKI for Node in TypeScript. (Keywords: Javascript, PKCS#11, Crypto, Smart Card, HSM)
MIT License
161 stars 34 forks source link

Get certificate signature #107

Closed LumaRay closed 5 years ago

LumaRay commented 5 years ago

Hello. How can I get a signature value from a certificate stored on a PKCS#11 hardware token like Rutoken ECP 2.0 using this Graphene library?

microshine commented 5 years ago

You need to use ASN.1 parser. You can use PKIjs module for it

LumaRay commented 5 years ago

Ho do I work with PKCS#11 hardware tokens from PKIjs? I cannot find it among the examples. I need to export CMS detached signature using GOST algorythm, Yury has suggested this example: https://github.com/PeculiarVentures/PKI.js/tree/master/examples/CMSSignedComplexExample But I need to modify it to use certificate from a token, and I need to sign CMS by a token using Kryptoki interface. Need examples badly :)

microshine commented 5 years ago

PKIjs supports Crypto Engines. You can create your own engine which will implement GOST algorithms.

You can use this code for example. It implements unsupported WebCrypto algorithms like RC2 an DES.

LumaRay commented 5 years ago

Thanks, I know that - Yury Strozhevsky has told me about writing that engine... but writing an engine is a comples task, I'd rather assemble ASN.1 structure of CMS and save it as a DER .sig file, all I need for it is a certificate from a token, a message digest and then sign the CMS.

I try this way, "cert" is imported, but the last line fails, why?

var graphene = require("graphene-pk11");
var asn1js = require("asn1js");
var Module = graphene.Module;

var mod = Module.load("C://Windows/System32/rtPKCS11ECP.dll", "Rutoken");

var slot = mod.getSlots(0);
if (slot.flags & graphene.SlotFlag.TOKEN_PRESENT) {

    var session = slot.open(graphene.SessionFlag.SERIAL_SESSION | graphene.SessionFlag.RW_SESSION);
    session.login("12345678");

    const certificates = session.find({ class: graphene.ObjectClass.CERTIFICATE });

    const cert = certificates.items(0).toType(); 

    const asn1 = asn1js.fromBER(cert.value);
}
microshine commented 5 years ago

I think the problem is in data type. cert.value is Buffer, but fromBER requires ArrayBuffer

Try new Uint8Array(cert.value).buffer

LumaRay commented 5 years ago

It worked, thanks! Now I stepped little further :) Actually Rutoken't Cryptoki library has few extended fuctions that perform that type of PKCS#7 signing/verification https://dev.rutoken.ru/pages/viewpage.action?pageId=3178555#id-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B9%D1%80%D0%B0%D1%81%D1%88%D0%B8%D1%80%D0%B5%D0%BD%D0%B8%D1%8F-C_EX_PKCS7Sign()

But I need a universal solution that would work with other HSMs as well.

microshine commented 5 years ago

It would be nice to implement GOST algorithms in WebCrypto API. It'd allow to use GOST for CMS, XMLDSig, XAdES, JOSE creation

LumaRay commented 5 years ago

Well, from what I've found, there are few javascript implementations: http://gostcrypto.com/ https://github.com/rudonick/crypto But those are not using HSMs' internal crypto providers.

microshine commented 5 years ago

To implement GOST we need:

LumaRay commented 5 years ago

Well, lots of work, but... hell it's useful!

LumaRay commented 5 years ago

Strange things happen... When this code is executed:

const signedAttr = [];

signedAttr.push(new Attribute({
    type: "1.2.840.113549.1.9.3",// contentType (PKCS #9)
    values: [
        new asn1js.ObjectIdentifier({ value: "1.2.840.113549.1.7.1" })// data (PKCS #7)
    ]
})); // contentType

signedAttr.push(new Attribute({
    type: "1.2.840.113549.1.9.5",// signingTime (PKCS #9)
    values: [
        new asn1js.UTCTime({ valueDate: new Date() })
    ]
})); // signingTime

signedAttr.push(new Attribute({
    type: "1.2.840.113549.1.9.4",// messageDigest (PKCS #9)
    values: [
        new asn1js.OctetString({ valueHex: signature.toString("hex") })/////////////////////
    ]
})); // messageDigest

cmsSignedSimpl.signerInfos[0].signedAttrs = new SignedAndUnsignedAttributes({
    type: 0,
    attributes: signedAttr//result// signedAttr
});

console.log(cmsSignedSimpl.signerInfos[0].signedAttrs);
data = cmsSignedSimpl.signerInfos[0].signedAttrs.toSchema(true).toBER(false);

It faild with this error:

SignedAndUnsignedAttributes {
  type: 0,
  attributes:
   [ Attribute { type: '1.2.840.113549.1.9.3', values: [Array] },
     Attribute { type: '1.2.840.113549.1.9.5', values: [Array] },
     Attribute { type: '1.2.840.113549.1.9.4', values: [Array] } ],
  encodedValue: ArrayBuffer { byteLength: 0 } }
App threw an error during load
RangeError: Source is too large
    at Uint8Array.set (<anonymous>)
    at utilConcatBuf (d:\_One\electron-test\node_modules\pvutils\build\utils.js:270:12)
    at OctetString.toBER (d:\_One\electron-test\node_modules\asn1js\build\asn1.js:850:39)
    at LocalConstructedValueBlock.toBER (d:\_One\electron-test\node_modules\PKIjs\node_modules\asn1js\build\asn1.js:1109:35)
    at Set.toBER (d:\_One\electron-test\node_modules\PKIjs\node_modules\asn1js\build\asn1.js:848:59)
    at LocalConstructedValueBlock.toBER (d:\_One\electron-test\node_modules\PKIjs\node_modules\asn1js\build\asn1.js:1109:35)
    at Sequence.toBER (d:\_One\electron-test\node_modules\PKIjs\node_modules\asn1js\build\asn1.js:848:59)
    at LocalConstructedValueBlock.toBER (d:\_One\electron-test\node_modules\PKIjs\node_modules\asn1js\build\asn1.js:1109:35)
    at Constructed.toBER (d:\_One\electron-test\node_modules\PKIjs\node_modules\asn1js\build\asn1.js:848:59)
    at Object.<anonymous> (d:\_One\electron-test\app.js:517:66)
YuryStrozhevsky commented 5 years ago

Try to debug yourself. The valueHex has ArrayBuffer type, not string with hex representation.

LumaRay commented 5 years ago

Thank you Yury, sorry, I was mislead by that "Hex" affix.

LumaRay commented 5 years ago

Sorry guys for bothering you again. I need to set this in ASN.1:

      SET (1 elem)
        SEQUENCE (2 elem)
          OBJECT IDENTIFIER 1.2.643.7.1.1.2.2 gost2012Digest256 (GOST R 34.11-2012 256 bit digest)
          NULL

I add it this way:

    cmsSignedSimpl = new SignedData({
        version: 1,
        digestInfo: new DigestInfo({
            digestAlgorithm: new AlgorithmIdentifier({algorithmId: "1.2.643.7.1.1.2.2"})
        }),
        encapContentInfo: new EncapsulatedContentInfo({
            eContentType: "1.2.840.113549.1.7.1" // "data" content type
        }),
        signerInfos: [
            new SignerInfo({
                version: 1,
                sid: new IssuerAndSerialNumber({
                    // issuer: certSimpl.issuer,
                    // serialNumber: certSimpl.serialNumber
                    issuer: certificate.issuer,
                    serialNumber: certificate.serialNumber
                })
            })
        ],
        //certificates: [certSimpl]
        certificates: [certificate]
    });

But it is not added...

LumaRay commented 5 years ago

This worked:

digestAlgorithms: [new DigestInfo({
    digestAlgorithm: new AlgorithmIdentifier({algorithmId: "1.2.643.7.1.1.2.2"})
})],

But it gives this instead:

      SET (1 elem)
        SEQUENCE (2 elem)
          SEQUENCE (1 elem)
            OBJECT IDENTIFIER 1.2.643.7.1.1.2.2 gost2012Digest256 (GOST R 34.11-2012 256 bit digest)
          OCTET STRING (0 elem)

Is this the same?

microshine commented 5 years ago

https://tools.ietf.org/html/rfc5652

SignedData ::= SEQUENCE {
        version CMSVersion,
        digestAlgorithms DigestAlgorithmIdentifiers,
        encapContentInfo EncapsulatedContentInfo,
        certificates [0] IMPLICIT CertificateSet OPTIONAL,
        crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
        signerInfos SignerInfos }

DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier

DigestAlgorithmIdentifier ::= AlgorithmIdentifier

Try

digestAlgorithms: [
  new AlgorithmIdentifier({algorithmId: "1.2.643.7.1.1.2.2"}),
],

https://github.com/PeculiarVentures/PKI.js/blob/d967c765f01c08914df626a4fe7156e56e7ef80f/src/SignedData.js#L984

LumaRay commented 5 years ago

Try

Thanks again! I added like this to get that "NULL" parameter as well:

digestAlgorithms: [
    new AlgorithmIdentifier({algorithmId: "1.2.643.7.1.1.2.2", algorithmParams: new asn1js.Null()}),
],
LumaRay commented 5 years ago

Strange thing is that using your library I get this format: https://www.screencast.com/t/qbfriw8pQTl With this code:

cmsSignedSimpl = new SignedData({
    version: 1,
    digestAlgorithms: [
        new AlgorithmIdentifier({algorithmId: "1.2.643.7.1.1.2.2", algorithmParams: new asn1js.Null()}),
    ],
    encapContentInfo: new EncapsulatedContentInfo({
        eContentType: "1.2.840.113549.1.7.1" // "data" content type
    }),
    signerInfos: [
        new SignerInfo({
            version: 1,
            sid: new IssuerAndSerialNumber({
                issuer: certificate.issuer,
                serialNumber: certificate.serialNumber
            }),
            digestAlgorithm: new AlgorithmIdentifier({algorithmId: "1.2.643.7.1.1.2.2", algorithmParams: new asn1js.Null()}),
            signature: new asn1js.OctetString({ valueHex: signature })
        })
    ],
    certificates: [certificate]
});

But when I create a signature using Kontur Signature service which uses Crypto PRO provider and another token, which is accepted by our government portal Gosuslugi, it's structure is different: https://www.screencast.com/t/FhczQl2P And I just don't understand how to put that public key entry instead of a signature in there.

microshine commented 5 years ago

@LumaRay Please call me on Skype:microshine82

LumaRay commented 5 years ago

Thank you Stepan! That was a really fruitful help!! Finally we got that thing working :) I've attached a working code sample.

code.zip