Skip to content

Instantly share code, notes, and snippets.

@My1
Created October 6, 2019 19:30
Show Gist options
  • Save My1/7ab5c4b154164331ac2ef18a65fd8a64 to your computer and use it in GitHub Desktop.
Save My1/7ab5c4b154164331ac2ef18a65fd8a64 to your computer and use it in GitHub Desktop.
<?php
$dbhost="localhost";
$dbname="db";
$dbuser="user";
$dbpass="P4$sW0rd";
$table="table";
//I use mysql for data storage
$link = mysqli_connect($dbhost,$dbuser,$dbpass,$dbname) or die("nocon");
//turn the binary AAGUID into something we can read
function bin2uuid($bin) {
$uuidReadable = unpack("H*",$bin);
$uuidReadable = preg_replace("/([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})/", "$1-$2-$3-$4-$5", $uuidReadable);
$uuidReadable = array_merge($uuidReadable)[0];
return $uuidReadable;
}
//we need the lib ( https://github.com/lbuchs/WebAuthn )
require_once '../WebAuthn.php';
//timeout in seconds
$timeout=180;
//attestation formats, use these to allow or disallow certain types of webauthn keys. packed is Fido2, the rest kinda self explanatory, by allowing none any key can get in by refusing attestation, setting none only it disables asking for attestation
$formats=array('none','android-key','android-safetynet','fido-u2f','packed');
$WebAuthn = new \WebAuthn\WebAuthn('My1s Webauthn Test', $_SERVER["HTTP_HOST"], $formats);
if(isset($_POST["reg"])) {
//set userid. username and displayname
$uid=$_POST["uid"];
$uname=$_POST["uname"];
$dname=$_POST["dname"];
$uv=$_POST["uv"];
//force userid to be alphanumeric to avoid bad people
if(!ctype_alnum($uid)) {
die("invalid uid");
}
//set residentkey flag depending on checkbox
if(isset($_POST["rk"])) {
$rk=true;
}
else {
$rk=false;
}
//prepare an empty array for existing keys (for preventing duplicates)
$exist=[];
//*
$res=mysqli_fetch_all(mysqli_query($link,"select credid from webauthn where uid='$uid'"));
foreach ($res as $line) {
$exist[]=base64_decode($line[0]);
}
//*/
$createArgs = json_encode($WebAuthn->getCreateArgs($uid, $uname, $dname, $timeout, $rk,$uv,$exist));
//start a session to store the userid for the device and the challenge
session_start();
$_SESSION['challenge'] = $WebAuthn->getChallenge();
$_SESSION['uid']=$uid;
}
if(isset($_POST["regdata"])) {
//restart the session so we can pull the challenge and userid
session_start();
$challenge=$_SESSION["challenge"];
$uid=$_SESSION["uid"];
//decode our json decode the base64 to binary and pass it to the library to actually do something
$r=json_decode($_POST["regdata"]);
$clientDataJSON = base64_decode($r->clientDataJSON);
$attestationObject = base64_decode($r->attestationObject);
$data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $challenge);
//pull the relevant data out of the objecr
$data->credentialId=base64_encode($data->credentialId);
$data->AAGUID=bin2uuid($data->AAGUID);
$data->signatureCounter=($data->signatureCounter === NULL ? 0 : $data->signatureCounter);
//var_dump($data);
//locate the columns we want to act upon
$cols="uid,credid,pk".($data->signatureCounter ? ",counter" : '').($data->certificate ? ",cert" : '').($data->AAGUID!=="00000000-0000-0000-0000-000000000000" ? ",aaguid" : '');
//set the values
$vals="'$uid','{$data->credentialId}','{$data->credentialPublicKey}'".($data->signatureCounter ? ",'{$data->signatureCounter}'" : '').($data->certificate ? ",'{$data->certificate}'" : '').($data->AAGUID!=="00000000-0000-0000-0000-000000000000" ? ",'{$data->AAGUID}'" : '');
//build the query
$q="insert into webauthn ($cols) values ($vals)";
//echo $q;
//send it off
mysqli_query($link,$q) or die(mysqli_error($link));
}
//prepare the login
if(isset($_POST["sig"])) {
$uid=$_POST["uid"];
$uv=$_POST["uv"];
//make sure userid is alphanumeric to prevent bad people doing bad stuff
if(!empty($uid) && !ctype_alnum($uid)) {
die("invalid uid");
}
//prepare array for existing keys
$exist=[];
//* read the existing Keys of the given user, if given
if($uid) {
$res=mysqli_fetch_all(mysqli_query($link,"select credid from webauthn where uid='$uid'"));
foreach ($res as $line) {
$exist[]=base64_decode($line[0]);
}
}
//*/
//get the args for the webauthn function and encode them.
$args=($WebAuthn->getGetArgs($exist,$timeout,true,true,true,true,$uv));
$getArgs = json_encode($args);
//pargs is only for looking nice and debugging
$pargs=json_encode($args,JSON_PRETTY_PRINT);
// store the challenge in the PHP session.
session_start();
$_SESSION['challenge'] = $WebAuthn->getChallenge();
}
//do the login
if(isset($_POST["sigdata"])) {
//restart the session to pull up the challenge
session_start();
//decode the data
$s=json_decode($_POST["sigdata"]);
$challenge=$_SESSION["challenge"];
$clientDataJSON = base64_decode($s->clientDataJSON);
$authenticatorData = base64_decode($s->authenticatorData);
$signature = base64_decode($s->signature);
$id=$s->id;
//check that credentialId is base64
if(!preg_match('/^[a-zA-Z0-9\/+]*={0,2}$/', $id)) {
die("invalid id");
}
//pull up userid public key and counter values for the given credentialId
$res=mysqli_fetch_array(mysqli_query($link,"select uid,pk,counter from webauthn where credid='$id'"),MYSQLI_ASSOC);
//have the library verify the stuff
$WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $res['pk'], $challenge, $res['counter'],($uv>0));
echo "{$res['uid']} Successfully Signed in.";
//update the counter
$q="update webauthn set counter={$WebAuthn->getSignatureCounter()} where credid='$id'";
//die($q);
mysqli_query($link,$q) or die("update fail");
}
//create base HTML with sandbox input form
echo <<<end
<html>
<head>
<title>My1s WebAuthn test</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<form method="post">
<input type="text" name="uname" placeholder="User Name" value=""/>
<input type="text" name="dname" placeholder="Secondary Display Name" value=""/>
<input type="text" name="uid" placeholder="User ID" value=""/>
<label for="uv">User Verification (PIN, fingerprint, etc.)</label>
<select type="dropdown" id="uv" name="uv">
<option value="discouraged">discouraged, try to skip verificatiion</option>
<option value="preferred" selected>preferred, verify the user if possible, else don't</option>
<option value="required">required, enforce user verification</option>
</select>
<input type="checkbox" name="rk" id="rk"/><label for="rk">Resident Key (forces UV)</label>
<button type="submit" name="reg">Reg</button>
<button type="submit" name="sig">Sign in</button>
</form>
<form id="regform" method="post">
<input type="text" name="regdata" id="regdata" placeholder="registration data"/>
</form>
</form>
<form id="sigform" method="post">
<input type="text" autocomplete="off" name="sigdata" id="sigdata" placeholder="login data"/>
</form>
end;
//add registration button and script
if(isset($createArgs)) {
echo <<<end
<button onclick="webreg()">Sign Up</button>
<script>
var args=$createArgs;
function recursiveBase64StrToArrayBuffer(r){if("object"==typeof r)for(let t in r)if("string"==typeof r[t]){let n=r[t];if("?BINARY?B?"===n.substring(0,"?BINARY?B?".length)&&"?="===n.substring(n.length-"?=".length)){n=n.substring("?BINARY?B?".length,n.length-"?=".length);let f=window.atob(n),o=f.length,i=new Uint8Array(o);for(var e=0;e<o;e++)i[e]=f.charCodeAt(e);r[t]=i.buffer}}else recursiveBase64StrToArrayBuffer(r[t])}function arrayBufferToBase64(r){for(var e="",t=new Uint8Array(r),n=t.byteLength,f=0;f<n;f++)e+=String.fromCharCode(t[f]);return window.btoa(e)}
recursiveBase64StrToArrayBuffer(args);
function webreg() {
navigator.credentials.create(args)
.then(result => {
r={};
r.clientDataJSON = result.response.clientDataJSON ? arrayBufferToBase64(result.response.clientDataJSON) : null;
r.attestationObject = result.response.attestationObject ? arrayBufferToBase64(result.response.attestationObject) : null;
document.getElementById("regdata").value=JSON.stringify(r);
document.getElementById("regform").submit();
})
.catch(e => {
window.exc=e;
console.log(e.message);
});
}
</script>
end;
}
//add script and button for logging in.
if(isset($getArgs)) {
echo <<<end
<pre>
$pargs
</pre>
<button onclick="webauth()">Login</button>
<script>
var args=$getArgs;
function recursiveBase64StrToArrayBuffer(r){if("object"==typeof r)for(let t in r)if("string"==typeof r[t]){let n=r[t];if("?BINARY?B?"===n.substring(0,"?BINARY?B?".length)&&"?="===n.substring(n.length-"?=".length)){n=n.substring("?BINARY?B?".length,n.length-"?=".length);let f=window.atob(n),o=f.length,i=new Uint8Array(o);for(var e=0;e<o;e++)i[e]=f.charCodeAt(e);r[t]=i.buffer}}else recursiveBase64StrToArrayBuffer(r[t])}function arrayBufferToBase64(r){for(var e="",t=new Uint8Array(r),n=t.byteLength,f=0;f<n;f++)e+=String.fromCharCode(t[f]);return window.btoa(e)}
recursiveBase64StrToArrayBuffer(args);
function webauth() {
navigator.credentials.get(args)
.then(result => {
window.res=result;
r={};
r.clientDataJSON = result.response.clientDataJSON ? arrayBufferToBase64(result.response.clientDataJSON) : null;
r.authenticatorData = result.response.authenticatorData ? arrayBufferToBase64(result.response.authenticatorData) : null;
r.signature = result.response.signature ? arrayBufferToBase64(result.response.signature) : null;
r.id = arrayBufferToBase64(result.rawId);
document.getElementById("sigdata").value=JSON.stringify(r);
document.getElementById("sigform").submit();
})
.catch(e => {window.exc=e;console.log(e.message);document.getElementById("sigdata").value=e.message});
}
</script>
end;
}
//end the HTML
echo <<<end
</body>
</html>
end;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment