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>
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With