Click here to Skip to main content
15,867,756 members
Articles / Web Development / HTML

Sign and Verify a form with Javascript

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
5 Jul 2016CPOL1 min read 31.9K   898   11   7
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)


Written By
Software Developer
Greece Greece
I'm working in C++, PHP , Java, Windows, iOS, Android and Web (HTML/Javascript/CSS).

I 've a PhD in Digital Signal Processing and Artificial Intelligence and I specialize in Pro Audio and AI applications.

My home page: https://www.turbo-play.com

Comments and Discussions

 
QuestionHi Mike I have a doubts to sign with .Key + . Cer files and password Pin
Hiber Tadeo1-Nov-20 10:08
Hiber Tadeo1-Nov-20 10:08 
QuestionInvalid digital Signature! Pin
Member 138620836-Jun-18 18:42
Member 138620836-Jun-18 18:42 
QuestionHi, Mike. Pin
gprokopan9-Jun-17 1:27
gprokopan9-Jun-17 1:27 
AnswerRe: Hi, Mike. Pin
Michael Chourdakis9-Jun-17 5:45
mvaMichael Chourdakis9-Jun-17 5:45 
GeneralRe: Hi, Mike. Pin
gprokopan11-Jun-17 20:25
gprokopan11-Jun-17 20:25 
QuestionQuestion about WebCrypto API Pin
Tomice7-Jul-16 20:50
Tomice7-Jul-16 20:50 
Js can not be trusted and js should not have access to a private key and password (just think about XSS or. MITM attacks).

Does WebCrypto API have access to crypto service provider? (ActiveX CAPICOM)
AnswerRe: Question about WebCrypto API Pin
Michael Chourdakis7-Jul-16 22:59
mvaMichael Chourdakis7-Jul-16 22:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.