17

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>
fracz
  • 20,536
  • 18
  • 103
  • 149
lumee
  • 613
  • 1
  • 5
  • 15

1 Answers1

13

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

Darlesson
  • 5,742
  • 2
  • 21
  • 26
pedrofb
  • 37,271
  • 5
  • 94
  • 142
  • Is there a predefined html+css template I have to follow with the forge library. I get error like: "Uncaught TypeError: Cannot read property 'Class' of undefined pkcs12.js:109" – lumee May 25 '16 at 20:59
  • also: util.js:1569 Uncaught TypeError: input.replace is not a function util.js.1569 and several other errors. I downloaded a recent version from github – lumee May 25 '16 at 21:01
  • Forge is pure javascript. You do not need html or css. ¿have you built the minified .js? Do not import the source files one by one because are dependencies. Please, also provide details of the code you are executing – pedrofb May 25 '16 at 21:22
  • I've updated the code I've been working on, I have now the minified version of forge and get a different error, I went into the code and found this line where it happens t.decode64=function(e){e=e.replace(/[^A-Za-z0-9\+\/\=]/g,""); – lumee May 27 '16 at 22:57
  • I guess you are using a variable of type file. Decode the content of the file, not the variable itself. Use readAsArrayBuffer and convert uint8 to String – pedrofb May 28 '16 at 08:37
  • It works fine, but in order to verify the signature I would need to upload the certificate. How could I serialize it with forge? – Michael Chourdakis Jul 03 '16 at 14:39
  • 1
    You can store the public certificate (`safeBag.type === forge.pki.oids.certBag && safeBag.attributes.localKeyId`) in PEM format (Base64 encoded). Use the following forge function: `forge.pki.certificateToPem(safeBag.cert);` To decode the cert use `forge.pki.certificateFromPem(certificatePem);` – pedrofb Jul 04 '16 at 06:54
  • @pedrofb there doesn't seem to be a function like forge.pki.certificateToPem in the documentation, but in code it works great – lumee Jul 11 '16 at 22:02
  • What web browsers does forge library support ? – Libor B. Nov 18 '16 at 11:24
  • It is a pure javascript library. It should work in any browser. Personally I've tried it on all those supported by the webcryptography api (except opera) See http://caniuse.com/#feat=cryptography – pedrofb Nov 18 '16 at 12:46