Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to load a PKCS#12 Digital Certificate with Javascript WebCrypto API

I'm trying to sign data using the WebCrypto API, but instead of creating a private/public key and exporting it to pkcs#1 or 8, I would really like to use a user's PKCS#12 to sign data. I've read the W3C spec, but cannot make much of it and can't find any good material on how to do this. Right now I want to leave ActiveX and Java Applets aside. Is there a way to tweak the following:

var buffer = encode(prompt("Please enter your password"));
    //TODO:
    //implement a prompt for a pfx or cert

  return crypto.subtle.importKey("raw", buffer, "PBKDF2", false, usages);
    //TODO:
    //instead of importing it, ask for the certificate's pass to sign data
    //with crypto.subtle.sign

Any pointers?

UPDATE Here's the code I've been working

<script src="forge.min.js"></script>

<script>
    var errorsReportedByVerifier;
    errorsReportedByVerifier = checkStorage() && checkBrowserAPIs();
    if (!errorsReportedByVerifier){
        console.log("adding click event");
        document.getElementById('btnPfx').addEventListener('click', handlePFXFile, false);
        storeVariables();
        getVariables();
    }


    function handlePFXFile(evnt) {
        console.log("handling pfx")
        //alert(document.getElementById('pfx').value);

        //error happens in 1st line
        //error object does not accept property replace
        //forge.min.js Line 1, Column: 17823
        var p12Der = forge.util.decode64(document.getElementById('pfx').valueOf());
        //var pkcs12Asn1 = forge.asn1.fromDer(p12Der);
        //var pkcs12 = forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, 'pss');
        console.log("pkcs12");
    }
</script>
like image 579
lumee Avatar asked Mar 15 '16 17:03

lumee


1 Answers

Web cryptography api does not support PKCS # 12. You can use a third party library to decode the p12 as forge https://github.com/digitalbazaar/forge#pkcs12 and load privateKey in webcrypto

Reading the PKCS#12 certificate

PKCS#12 is stored in DER, so first read it from a File or use a pre-stored base64

//Reading certificate from a 'file' form field
var reader = new FileReader();
reader.onload = function(e) {               
    var contents = e.target.result;
    var pkcs12Der = arrayBufferToString(contents)
    var pkcs12B64 = forge.util.encode64(pkcs12Der);     
    //do something else...

}   
reader.readAsArrayBuffer(file);

function arrayBufferToString( buffer ) {
    var binary = '';
    var bytes = new Uint8Array( buffer );
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode( bytes[ i ] );
    }
    return binary;
}

//p12 certificate stored in Base64 format
var pkcs12Der= forge.util.decode64(pkcs12B64);

Decode PKCS#12 with forge and extract private key

Then decode DER format to ASN1, and let forge reads the content

var pkcs12Asn1 = forge.asn1.fromDer(pkcs12Der);
var pkcs12 = forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, password);

Then get the private key from pkcs12 of the desired certificate (see forge doc) and convert to PKCS # 8 to be imported with webcrypto

// load keypair and cert chain from safe content(s) 
for(var sci = 0; sci < pkcs12.safeContents.length; ++sci) {
    var safeContents = pkcs12.safeContents[sci];

    for(var sbi = 0; sbi < safeContents.safeBags.length; ++sbi) {
        var safeBag = safeContents.safeBags[sbi];

        // this bag has a private key
        if(safeBag.type === forge.pki.oids.keyBag) {
            //Found plain private key
            privateKey = safeBag.key;
        } else if(safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) {
            // found encrypted private key
            privateKey = safeBag.key;
        } else if(safeBag.type === forge.pki.oids.certBag) {
            // this bag has a certificate...        
        }   
    }
}

Convert to PKCS#8

function _privateKeyToPkcs8(privateKey) {
     var rsaPrivateKey = forge.pki.privateKeyToAsn1(privateKey);
     var privateKeyInfo = forge.pki.wrapRsaPrivateKey(rsaPrivateKey);
     var privateKeyInfoDer = forge.asn1.toDer(privateKeyInfo).getBytes();
     var privateKeyInfoDerBuff = stringToArrayBuffer(privateKeyInfoDer);
     return privateKeyInfoDerBuff;
 }
 function stringToArrayBuffer(data){
     var arrBuff = new ArrayBuffer(data.length);
     var writer = new Uint8Array(arrBuff);
     for (var i = 0, len = data.length; i < len; i++) {
         writer[i] = data.charCodeAt(i);
     }
     return arrBuff;
  }

Import key in Webcrypto

And finally import the key in webcrypto

function _importCryptoKeyPkcs8(privateKey,extractable) {
    var privateKeyInfoDerBuff = _privateKeyToPkcs8(privateKey);

    //Import the webcrypto key
    return crypto.subtle.importKey(
            'pkcs8', 
            privateKeyInfoDerBuff, 
            { name: "RSASSA-PKCS1-v1_5", hash:{name:"SHA-256"}},
            extractable, 
            ["sign"]);        

}
_importCryptoKeyPkcs8(entry.privateKey,extractable).    
        then(function(cryptoKey) {
            //your cryptokey is here!!!
        });

Digital signature

With the imported cryptoKey returned from the above method you can sign with webcrypto.

var digestToSign = forge.util.decode64(digestToSignB64);
var digestToSignBuf = stringToArrayBuffer(digestToSign);

crypto.subtle.sign(
            {name: "RSASSA-PKCS1-v1_5"},
            cryptoKey,
            digestToSignBuf)
.then(function(signature){
    signatureB64 = forge.util.encode64(arrayBufferToString(signature))
});

I include coding from base64 because data conversions are not trivial

In pkc12 you also have the certification chain if you need to build advanced formats like AdES

like image 99
pedrofb Avatar answered Sep 22 '22 21:09

pedrofb