Building a secure login

Today I learned about the keygen html5 tag, you read that right. HTML5 <keygen> tag, I couldn’t stop myself I needed to make something. Could it be so simple? A password less login all generated from browser keys? It turns out it was/is.

One Caveat, from everything I read Microsoft/Internet Explorer have no intentions of supporting this tag, but basically everyone else does. The keygen tag works as a form element, it posts the public key while storing the private key in it’s own localStorage html5 database (very cool), after we generate the key you can allow the person access to the secure area because they have installed your key in say there “keychain” on a Mac.

Here is some code

<form action="index.php" method="post">Username: <input name="username" type="text" />
 
<input name="createcert" type="submit" value="Generate" />
 
</form>

This code makes a plain Jane page that asks for a username and generates a key, it posts the key in spkac format to the server in our case the variable is $_POST[‘pubkey’].

The page looks like this:
Screen Shot 2014-09-12 at 9.38.36 AM

Now we need some server code to make this work.

if (isset($_POST['pubkey'])) {
        $username=$_POST['username'];
        $key = $_REQUEST['pubkey'];
        $keyreq = "SPKAC=".str_replace(str_split(" \t\n\r\0\x0B"), '', $key);
        $keyreq .= "\nCN=$username";
        $keyreq .= "\nemailAddress=john@thebugshop.net";
        $keyreq .= "\n0.OU=website client certificate";
        $keyreq .= "\norganizationName=Website";
        $keyreq .= "\ncountryName=US";
        $keyreq .= "\nstateOrProvinceName=Iowa";
        $keyreq .= "\nlocalityName=Spencer";
        write_cert($keyreq);
}

Now obviously the stuff in the key should all be user generated, but for my simplistic sake, this stuff is static.

the write_cert() function is where the magic happens.

function write_cert($key) {
        $t=tempnam("/tmp","key-");
        $tmp = fopen($t,"w");
        fwrite($tmp, $key);
        fclose($tmp);
        $days = "180";
        $done = tempnam("/tmp","clientkey-");
        $capw = "********";
        $command = "/usr/bin/openssl ca -config /etc/ssl/openssl.cnf -days ".$days." -notext -batch -spkac ".$t." -out ".$done." -passin pass:'".$capw."' 2&gt;&amp;1";
        $output = shell_exec($command);
        $fp = fopen("/tmp/error","w");
        fwrite($fp, $output);
        fclose($fp);
        #error_log($output);
        $length = filesize($done);      // $cerfolder.$uniq = /tmp/4k9Bal
        error_log($length);
        header('Last-Modified: '.date('r+b'));
        header('Accept-Ranges: bytes');
        header('Content-Length: '.$length);
        header('Content-Type: application/x-x509-user-cert');
        readfile($done);
        #unlink($done);
        exit;
 
}

This creates a cert that will last for 180 days, and sends the file to the clients browser. The install goes something like this:
Screen Shot 2014-09-12 at 9.42.01 AM

Screen Shot 2014-09-12 at 9.42.24 AM

Screen Shot 2014-09-12 at 9.42.36 AM

and that’s it! You will have to create specific apache rules to make sure that only “valid” certificates are granted access.

  SSLEngine on
  SSLCipherSuite HIGH:MEDIUM
  SSLCertificateFile /etc/ssl/demoCA/certs/domain
  SSLCertificateKeyFile /etc/ssl/demoCA/certs/domain.pem
  SSLCACertificateFile /etc/ssl/demoCA/certs/ca.pem
  SSLCARevocationFile /etc/ssl/demoCA/certs/cacrl.pem
 
    SSLVerifyClient require
    SSLVerifyDepth 1

Then anytime anyone accesses your website @ https://yoursite.com/secure_area there installed certificate will be compared and they will be allowed access!

Very cool!

–John

Leave a Reply

Your email address will not be published. Required fields are marked *