NOTE/EDIT: Package name will change to Crypt::U2F::Server (and Crypt::U2F::Server::Simple), because there will also be a client module to access the key itself.

I just uploaded the first Alpha version of Crypt::U2F, which allows you to handle the server side cryptography of the FIDO alliance's Universal 2nd factor authentication method. See also here.

This is the same one used by Google services and fully supported in Google Chrome.

Internally, Crypt::U2F requires Yubico's libu2f-server library installed on your system. I implemented this in two Perl modules: Crypt::U2F is the low level module (sand subject to change), that let's you play around with the underlying library. Crypt::U2F::Simple is the one you should use in most cases.

Let's have a look into the two examples provided with the tarball. For this to work, you need to install libu2f-server and also install libu2f-host, because we need the u2f-host binary to talk to the actual USB dongle. (I'm currently in the process of making a Perl module for libu2f-host as well, but this will only finish after the hollidays.)

The whole thing is a two part process: First you have register a new key once, then you can authenticate as often as you like. Each part (registering, authentication) itself is a two-part process as well, first you generate a challenge and send it to the client, then you have to validate the response.

Ok, let's start with registering a key. For this example, we pass around files to and from u2f-host and also save the registered keyHandle and public key into files as well. In a real world scenario, you will probably use HTTP and Javascript to communicate with the key and save keyHandle and the public key into a database. Here's the code:

#!/usr/bin/env perl use strict; use warnings; BEGIN { unshift @INC, "../lib"; } my $u2fhost = '/usr/local/bin/u2f-host'; my $appId = 'Example'; my $origin = 'http://127.0.0.1'; use Crypt::U2F::Simple; use MIME::Base64; my $auth = Crypt::U2F::Simple->new(appId=>$appId, origin=>$origin); if(!defined($auth)) { die(Crypt::U2F::Simple::lastError()); } my $challenge = $auth->registrationChallenge(); if(!defined($challenge) || !length($challenge)) { die($auth->lastError()); } open(my $cofh, '>', 'regChallenge.dat') or die($!); print $cofh $challenge; close $cofh; my $regcmd = $u2fhost . ' -aregister -o "' . $origin . '" < regChallen +ge.dat > regReply.dat'; print "Running $regcmd...\nPlease press the blinking button!\n"; `$regcmd`; open(my $cifh, '<', 'regReply.dat') or die($!); my $reply = <$cifh>; close $cifh; print "Got $reply\n"; my ($keyHandle, $publicKey) = $auth->registrationVerify($reply); if(!defined($keyHandle)) { print "failed to get keyHandle!\n"; } if(!defined($publicKey)) { print "failed to get publicKey!\n"; } if(!defined($keyHandle) || !defined($publicKey)) { die($auth->lastError()); } open(my $kofh, '>', 'keyHandle.dat') or die($!); print $kofh $keyHandle; close $kofh; open(my $pofh, '>', 'publicKey.dat') or die($!); print $pofh encode_base64($publicKey, ''); close $pofh;

The reason we use Base64 is simple, yet annoying: Everything except the public key is either some sort of text or even ASCII JSON. The public key on the other hand is a binary blob. It's just a matter of convenience to turn it into Base64, because that we it works in textfiles and text columns in databases as well. It don't convert directly in the library, because that might make it problematic to cooperate with other implementations of U2F authentications that also use the original C library (which delivers a binary blob), including the u2f-server example binary that comes with it.

All of the calls to Crypt::U2F::Simple may fail for one reason or another (including new() and DESTROY()), so make sure you check all the return values!

Let's tackle the authentication part. We'll use the keyHandle.dat and publicKey.dat generated in the previous step:

#!/usr/bin/env perl use strict; use warnings; BEGIN { unshift @INC, "../lib"; } my $u2fhost = '/usr/local/bin/u2f-host'; my $appId = 'Example'; my $origin = 'http://127.0.0.1'; use Crypt::U2F::Simple; use MIME::Base64; open(my $kifh, '<', 'keyHandle.dat') or die($!); my $keyHandle = <$kifh>; close $kifh; open(my $pifh, '<', 'publicKey.dat') or die($!); my $publicKey = <$pifh>; $publicKey = decode_base64($publicKey); close $pifh; my $auth = Crypt::U2F::Simple->new(appId=>$appId, origin=>$origin, keyHandle=>$keyHandle, publicKey=> +$publicKey); if(!defined($auth)) { die(Crypt::U2F::Simple::lastError()); } my $challenge = $auth->authenticationChallenge(); if(!defined($challenge) || !length($challenge)) { die($auth->lastError()); } open(my $cofh, '>', 'authChallenge.dat') or die($!); print $cofh $challenge; close $cofh; my $regcmd = $u2fhost . ' -aauthenticate -o "' . $origin . '" < authCh +allenge.dat > authReply.dat'; print "Running $regcmd...\nPlease press the blinking button!\n"; `$regcmd`; open(my $cifh, '<', 'authReply.dat') or die($!); my $reply = <$cifh>; close $cifh; print "Got $reply\n"; my ($isValid) = $auth->authenticationVerify($reply); if($isValid) { print "Hurray! User has been verified as valid!\n"; } else { print "Oh no, verification failed! The reason is: ", $auth->lastEr +ror(), "\n"; }

As you can see, the process is quite similar: We load the keyHandle.dat and publicKey.dat (the second one we decode_base64()) and initialize Crypt::U2F::Simple with it. Then we generate a challenge and verify it.

If you want to make sure the verification step actually works, you can comment out the call can try to fuss the result of u2fhost in authReply.dat. Or just comment out the call to u2fhost after you you did one successfull authentication, this one should give you a u2fs_authentication_verify (-6): Challenge error.

Limitations and Bugs: Currently (Version 0.10), each Challenge/Verify combo has to run in the same instance of the module. I'm still working on finding out how to fix that. Also, sometimes the USB keyfob seems to be in a strange state after plugging in, returning wrongly calculated authentication replies (at least mine does). Unplugging and replugging solves that problem.

"For me, programming in Perl is like my cooking. The result may not always taste nice, but it's quick, painless and it get's food on the table."