How to load a PKCS#12 Digital Certificate with Javascript WebCrypto API
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 fieldvar 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 formatvar 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