Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Sign and Verify a form with Javascript

5.00/5 (7 votes)
6 Jul 2016CPOL1 min read 33.6K   903  
Client side signing and verification

Introduction

Α demonstration on how you create a form, require your user to sign it with their private key, and then verify the signature. I assume that you already know the pki mechanisms. I 've searched in various places, one helpful article is http://stackoverflow.com/questions/36018233/how-to-load-a-pkcs12-digital-certificate-with-javascript-webcrypto-api and others.

The APIs discussed here only work in a secure (or a local file) connection.

Signing with WebCrypto API

Usually we have a form which the user will fill, and we want a digital signature on it. For example, a form like that:

C++
//
<form name="form1" id="form1" method="post" action="">
  <label for="firstname">First name:</label>
  <input type="text" name="firstname" id="firstname" required><br>
  <label for="lastname">Last name:</label>
  <input type="text" name="lastname" id="lastname" required><br>
</form>
//

We can get the contains of this form with jQuery:

C++
//
$('#form1').serialize();
//

We will also have the user choose his PFX file and enter a Private Key password:

<label for="pfx">Select PFX/P12 file:</label><br>
<input name="pfx" type="file" id="pfx" accept=".pfx,.p12" required /><br>
 <label for="pfxp">Enter Private Key password:</label><br>
<input name="pfxp" type="password" id="pfxp" /><br>

We now need to read the PFX file with forge.js into a structure:

// Get PFX
    var fileInput = document.getElementById('pfx');
    var file = fileInput.files[0];

    // Read it
    var reader = new FileReader();
    reader.onload = function(e) 
        {
        var contents = e.target.result;
        var pkcs12Der = arrayBufferToString(contents)
        var pkcs12B64 = forge.util.encode64(pkcs12Der);
        var privateKey;
        var pkcs12Asn1 = forge.asn1.fromDer(pkcs12Der);
        var password = $('#pfxp').val();
        
        var pkcs12 = forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, password);
        // load keys
        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];
                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...
                    cert = safeBag.cert;
                    }    
                }
            }
       }
reader.readAsArrayBuffer(file); 

This will read our private key and the certificate into two variables, privatekey and cert. Now  we need to import it to a WebCrypto PKCS#8:

 

function importCryptoKeyPkcs8(privateKey,extractable) 
    {
    var privateKeyInfoDerBuff = privateKeyToPkcs8(privateKey);

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

 

And now we can sign:

importCryptoKeyPkcs8(privateKey,true).then(function(cryptoKey) 
            {
            // Imported!
            
            // Empty stuff
            var digestToSignBuf = stringToArrayBuffer(ser);
            var pem = forge.pki.certificateToPem(cert);
            $('#pfxc').val(forge.util.encode64(pem));

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

And we can store the original text, the certificate as PEM and the signature as base64.

Verifying the signature

For verification, we need the three items (data,signature,certificate):

 

// From Public Key to a PKCS#8
function publicKeyToPkcs8(pk) 
    {
    var subjectPublicKeyInfo = forge.pki.publicKeyToAsn1(pk);
    var der = forge.asn1.toDer(subjectPublicKeyInfo).getBytes();
    return stringToArrayBuffer(der);
    }

// Verify it
function Verify()
    {
    var pem = ...
    var signature64 = ...
    var signature = forge.util.decode64(signature64);
    var data = ...
    var cert = forge.pki.certificateFromPem(pem);

    // Import the certifcate
    window.crypto.subtle.importKey("spki",publicKeyToPkcs8(cert.publicKey),
        {   
        name: "RSASSA-PKCS1-v1_5",
        hash: {name: "SHA-256"}, 
        },
        false,
        ["verify"]
        ).then(function(k)
            {
            window.crypto.subtle.verify(
                {
                name: "RSASSA-PKCS1-v1_5",
                },
                k, //from generateKey or importKey above
                stringToArrayBuffer(signature), //ArrayBuffer of the signature
                stringToArrayBuffer(data) //ArrayBuffer of the data
                ).then(function(isvalid)
                {
                //returns a boolean on whether the signature is true or not
                   if (!isvalid)
                       {
                       }
                else
                       {
                         // Valid signature
                       }
                }).catch(function(err)
                    {
                       // Invalid sig or something not worked
                    });
                }
                
            );
    }

The HTML file

You can experiment with the attached HTML file. As of this release, not all certificates work with this procedure. If you find a bug, let me know!

History

06 - 07 - 2016 : First release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)