From 06caccc4882ebdbb93ef0d4a0f6650e692d4443a Mon Sep 17 00:00:00 2001 From: b1galez Date: Sat, 20 Nov 2010 23:19:51 +0000 Subject: Version 2.0 git-svn-id: http://yubico-yubiserve.googlecode.com/svn/trunk@4 fbcee277-3294-991b-8290-beb7048acdd6 --- README | 172 ++++++++++++++++++++++++++++++++++--------- dbconf.py | 204 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/dump.sql | 30 ++++++-- yubikeys.sqlite | Bin 5120 -> 11264 bytes yubiserve-config.php | 28 ------- yubiserve-utils.php | 80 -------------------- yubiserve.php | 88 ---------------------- yubiserve.png | Bin 0 -> 25307 bytes 8 files changed, 364 insertions(+), 238 deletions(-) create mode 100755 dbconf.py delete mode 100644 yubiserve-config.php delete mode 100644 yubiserve-utils.php delete mode 100644 yubiserve.php create mode 100644 yubiserve.png diff --git a/README b/README index 860f23e..8b5d27f 100644 --- a/README +++ b/README @@ -1,39 +1,141 @@ -YubiServe has been written by Alessio Periloso + + == Author & Version == +YubiServe has been written by Alessio Periloso Version 1.0: 21/05/2010 +Version 2.0: 19/11/2010 -This simple service allows to authenticate yubikeys using only a small -sqlite database. + == Description == +This simple service allows to authenticate Yubikeys and OATH Tokens using +only a small sqlite database. The code has been released under GNU license (license into LICENSE file) -To authenticate, call the script yubiserve.php with the parameter otp. -Ex.: yubiserve.php?otp=vviblrbuicuvevcnnedbuuhjfhrebjlchhidkfrdkike - -To add a new key, run ./keyconf.py -a -To delete a key, run ./keyconf.py -k -To disable a key (disabling a key won't delete it from the db), run - ./keyconf.py -d -To enable a key, run ./keyconf.py -e - - -The YubiServe service needs the php-sqlite, php-mcrypt and python-sqlite support. -On debian/ubuntu, you can run: -sudo apt-get install php5-mcrypt php5-sqlite python-sqlite - -About Apache configuration, it is **REALLY IMPORTANT** users are not -allowed to download the yubikeys.sqlite database. For this purpose, -I wrote the .htaccess file into yubiserve directory. Sometimes -however, into main Apache configuration the AllowOverride parameter -is set to "None", and the .htaccess won't be loaded at all. -If this is your case, be sure to add to your apache configuration the -following lines (change !!!): - - /> -#ex. - Order deny,allow - Deny from all - - - Order deny,allow - Allow from all - - +The project is divided into two parts: + - The database management tool (dbconf.py) + - The validation server (yubiserve.py) + + + == The database management tool == +The database management tool helps you to manage keys in the database. +For detailed help, run the database management tool with ./dbconf.py + +The tool allows you to add, delete, disable and enable keys/tokens. +You can also add and remove API keys, to check the server signature in +server responses. +Everything is managed through nicknames, to make keys easy to remember +who belong to. + +For example, to add a new yubikey, write: +./dbconf.py -ya alessio vvkdtkjureru 980a8608b307 f1dc9c6585d600d06f9aae1abea2969e + +In this example, 'alessio' is the key nickname, 'vvkdtkjureru' is the +key public identity (the one you can see at the beginning of your OTPs), +'980a8608b307' is the private identity of the OTP (you can read it when +you program your key), and the last parameter is the AES Key. + + +To add a new OATH/HOTP: +./dbconf.py -ha alessio 4rvn24642402 f03ddacdfebb6396f60d7045f41de68f5c5e1c3f + +In this other example, 'alessio' is still the nickname, '4rvn24642402' is +the public identity of the token (it could be also 1, 2, 'alessio' or +whatever you want; the Yubico implementation is 12 characters long) + + +To add a new API key: +./dbconf.py -aa alessio + +When you add a new API key, the configuration tool will return both +the api key (ex. 'UkxFMnNFNTV4clRYUExSOWlONzQ=') and the API key id +meant to be used later in your queries to the Yubiserve validation server. + + + == The Yubiserve Validation Server == +Understanding how to use the Yubiserve web application is pretty simple. +You just have to run it (./yubiserve.py) and send your queries through +HTTP GET connections. +The default listening port is 8000, the default listening ip is 0.0.0.0 +(so you can connect to it from other machines). If you need it to answer +only from local machine, you can change the ip to 127.0.0.1. +When you connect to the server (ex. http://192.168.0.1:8000/), it will +answer with a simple page, asking you Yubico Yubikeys OTPs or OATH/HOTP +tokens. +The Yubico Yubikey needs only one parameter: the OTP. +The OATH/HOTP tokens needs two parameters: the OTP itself (6 or 8 digits) +and the Token Identifier. The token identifier can be any character string +you prefer, or, according to the standard OATH implementation, the preceding +string to the OTP. The Yubico implementation follows this standard. +The Yubiserve Validation Server, according to the standard, will try to +find the Token Identifier preceding the OTP. If the string is found, the +OTP will be verified according to that string; in case of LCD tokens, +the string is not automatically added, so you will need to insert your ID +in the second box to allow the Validation Server to find your own identity. + + + == Querying the Yubiserve Validation Server == +Querying the Yubiserve Validation Server is pretty simple. +For Yubico Yubikeys, you will need to send a HTTP GET connection to: +http://:/wsapi/2.0/verify?otp= +ex.: http://192.168.0.1:8000/wsapi/2.0/verify?otp=vvnjbbkvjbcnhiretjvjfebbrdgrjjchdhtbderrdbhj +This way you will try to authenticate to it, the simplest way possible. +The response will be something like: + +otp=vvnjbbkvjbcnhiretjvjfebbrdgrjjchdhtbderrdbhj +status=OK +t=2010-11-20T23:54:35 +h= + +As you can see, the 'h' parameter is not set, and this is because we didn't use +the signature through API Key. To use it, just add the 'key=' +parameter we had when we added the API Key. +ex.: http://192.168.0.1:8000/wsapi/2.0/verify?otp=vvnjbbkvjbcnhiretjvjfebbrdgrjjchdhtbderrdbhj&id=1 +This time the response will be like: + +otp=vvnjbbkvjbcnhiretjvjfebbrdgrjjchdhtbderrdbhj +status=OK +t=2010-11-21T00:00:03 +h=6lrhQPKo1I/RQA1KPnjpuiOvVMc= + +To check the server signature, check the source code (you will have to do the +exact same procedure to generate it and then just check if they are equal), or +rely on the Yubico documentation on Validation Servers. + +For OATH/HOTP keys, the query can be simplified or not. +If your token supports the 'Token Identifier', like Yubico Yubikeys, you can just +send one parameter, the generated string, and the Yubiserve Validation Server will +take care of looking for your key informations in the database. +If your token instead only generates the 6-8 digits, you will have to explicit +your publicID through another parameter. +So, you will have to query, via HTTP GET, the following address: +http://:/wsapi/2.0/oathverify?otp=&publicid= +ex.: http://192.168.0.1:8000/wsapi/2.0/oathverify?otp=80l944311056173483 +ex.: ex.: http://192.168.0.1:8000/wsapi/2.0/oathverify?otp=173483&publicid=80l944311056 +Both the examples works the same way: in the first case, the Token Identifier was +inside the generated OTP (like in Yubico Yubikey implementation), in the second case +an authentication through a LCD Token was made, so the Yubiserve needed to know who +the token belonged to, and the publicid parameter was added. +The response, like Yubico Yubikey queries, is the following: + +otp=80l944311056173483 +status=OK +t=2010-11-21T00:04:59 +h= + +The 'h' parameter is not set, because we didn't specified the API Key id. To use the +server signature, we will need to add the 'id' parameter, like in the following query: +ex.: http://192.168.1.2:8000/wsapi/2.0/oathverify?otp=80l944311056173483&id=1 +ex.: http://192.168.0.1:8000/wsapi/2.0/oathverify?otp=173483&publicid=80l944311056&id=1 + +And this would be the the response: + +otp=80l944311056173483 +status=OK +t=2010-11-21T00:10:56 +h=vYoG9Av8uG6OqVkmMFuANi4fyWw= + + + == Final thoughts == + +That's all. Pretty simple, huh? +Of course you can add new keys while the server is already running, without needing it +to restart, and of course multiple queries a time are allowed, that's why the server +is multithreaded. \ No newline at end of file diff --git a/dbconf.py b/dbconf.py new file mode 100755 index 0000000..894efde --- /dev/null +++ b/dbconf.py @@ -0,0 +1,204 @@ +#!/usr/bin/python +import sqlite, time, random, re +from sys import argv + +def randomChars(max): + retVal = '' + for i in range(0, max): + rand = random.randrange(0, 63) + if (rand>36): + retVal += chr(rand-36+96) # Starting with 'a' + elif (rand>10): + retVal += chr(rand-10+64) # Starting with 'A' + else: # Starting with '0' + retVal += chr(rand+47) + return retVal + +con = sqlite.connect('yubikeys.sqlite') +cur = con.cursor() + +if (len(argv)<2): + print ' == YubiServe Key Management Tool 2.0 ==\n' + print ' -ya \tAdd a new Yubikey' + print ' -yk \t\t\t\t\tDelete a Yubikey' + print ' -yd \t\t\t\t\tDisable a Yubikey' + print ' -ye \t\t\t\t\tEnable a Yubikey' + print ' -yl\t\t\t\t\t\tList all yubikeys in database\n' + + print ' -ha \t\tAdd a new OATH token' + print ' -hk \t\t\t\t\tDelete a OATH token' + print ' -hd \t\t\t\t\tDisable a OATH token' + print ' -he \t\t\t\t\tEnable a OATH token' + print ' -hl\t\t\t\t\t\tList all OATH tokens in database\n' + + print ' -aa \t\t\t\t\tGenerate an API Key' + print ' -ak \t\t\t\t\tRemove an API Key' + print ' -al\t\t\t\t\t\tList all API Keys in database\n' + +else: + if argv[1][0:2] == '-y': # Yubico Yubikey + if (argv[1][2] == 'd') and (len(argv)>2): + nickname = re.escape(argv[2]) + cur.execute("SELECT * FROM yubikeys WHERE nickname = '" + nickname + "'") + if (cur.rowcount == 0): + print 'Key not found.' + else: + cur.execute("SELECT * FROM yubikeys WHERE nickname = '" + nickname + "' AND active = 'true'") + if (cur.rowcount == 1): + cur.execute("UPDATE yubikeys SET active = 'false' WHERE nickname = '" + nickname + "'") + print "Key '" + nickname + "' disabled." + con.commit() + else: + print 'Key is already disabled.' + + elif (argv[1][2] == 'e') and (len(argv)>2): + nickname = re.escape(argv[2]) + cur.execute("SELECT * FROM yubikeys WHERE nickname = '" + nickname + "'") + if (cur.rowcount == 0): + print 'Key not found.' + else: + cur.execute("SELECT * FROM yubikeys WHERE nickname = '" + nickname + "' AND active = 'false'") + if (cur.rowcount == 1): + cur.execute("UPDATE yubikeys SET active = 'true' WHERE nickname = '" + nickname + "'") + print "Key '" + nickname + "' enabled." + con.commit() + else: + print 'Key is already enabled.' + elif (argv[1][2] == 'k') and (len(argv)>2): + nickname = re.escape(argv[2]) + cur.execute("SELECT * FROM yubikeys WHERE nickname = '" + nickname + "'") + if (cur.rowcount == 0): + print 'Key not found.' + else: + cur.execute("DELETE FROM yubikeys WHERE nickname = '" + nickname + "'") + print "Key '" + nickname + "' deleted." + con.commit() + elif (argv[1][2] == 'a') and (len(argv)>4): + nickname = re.escape(argv[2]) + if ((len(argv[2])<=16) and (len(argv[3]) <= 16) and (len(argv[4]) <= 12) and (len(argv[5])<=32)): + cur.execute("SELECT * FROM yubikeys WHERE nickname = '" + argv[2] + "' OR publicname = '" + argv[3] + "'") + if (cur.rowcount == 0): + cur.execute("INSERT INTO yubikeys VALUES ('" + argv[2] + "', '" + argv[3] + "', '" + time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + "', '" + argv[4] + "', '" + argv[5] + "', 'true', 1, 1)") + con.commit() + print "Key '" + argv[2] + "' added to database." + else: + print 'Key is already into database. Delete it before adding the same key!' + else: + print 'Nickname and publicid must be max 16 characters long.' + print 'Secretid must be 12 characters max, aeskey must be 32 characters max.\n' + quit() + elif (argv[1][2] == 'l'): + cur.execute('SELECT nickname, publicname FROM yubikeys') + if cur.rowcount != 0: + print " " + str(cur.rowcount) + " keys into database:" + print '[Nickname]\t\t>> [PublicID]' + for i in range(0, cur.rowcount): + (nickname, publicname) = cur.fetchone() + print ' ' + nickname + ' ' * (23-len(nickname)) + ">> " + publicname + print '' + else: + print 'No keys in database\n' + else: + print 'Not enough parameters. Try looking at ' + argv[0] + ' --help' + elif argv[1][0:2] == '-h': + if (argv[1][2] == 'd') and (len(argv)>2): + nickname = re.escape(argv[2]) + cur.execute("SELECT * FROM oathtokens WHERE nickname = '" + nickname + "'") + if (cur.rowcount == 0): + print 'Key not found.' + else: + cur.execute("SELECT * FROM oathtokens WHERE nickname = '" + nickname + "' AND active = 'true'") + if (cur.rowcount == 1): + cur.execute("UPDATE oathtokens SET active = 'false' WHERE nickname = '" + nickname + "'") + print "Key '" + nickname + "' disabled." + con.commit() + else: + print 'Key is already disabled.' + + elif (argv[1][2] == 'e') and (len(argv)>2): + nickname = re.escape(argv[2]) + cur.execute("SELECT * FROM oathtokens WHERE nickname = '" + nickname + "'") + if (cur.rowcount == 0): + print 'Key not found.' + else: + cur.execute("SELECT * FROM oathtokens WHERE nickname = '" + nickname + "' AND active = 'false'") + if (cur.rowcount == 1): + cur.execute("UPDATE oathtokens SET active = 'true' WHERE nickname = '" + nickname + "'") + print "Key '" + nickname + "' enabled." + con.commit() + else: + print 'Key is already enabled.' + elif (argv[1][2] == 'k') and (len(argv)>2): + nickname = re.escape(argv[2]) + cur.execute("SELECT * FROM oathtokens WHERE nickname = '" + nickname + "'") + if (cur.rowcount == 0): + print 'Key not found.' + else: + cur.execute("DELETE FROM oathtokens WHERE nickname = '" + nickname + "'") + print "Key '" + nickname + "' deleted." + con.commit() + elif (argv[1][2] == 'a') and (len(argv)>3): + nickname = re.escape(argv[2]) + if (len(argv[2])<=16) and (len(argv[3]) <= 16) and (len(argv[4]) <= 40): + cur.execute("SELECT * FROM oathtokens WHERE nickname = '" + argv[2] + "' OR publicname = '" + argv[3] + "'") + if (cur.rowcount == 0): + cur.execute("INSERT INTO oathtokens VALUES ('" + nickname + "', '" + argv[3] + "', '" + time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + "', '" + argv[4] + "', 'true', 1)") + con.commit() + print "Key '" + argv[2] + "' added to database." + else: + print 'Key is already into database. Delete it before adding the same key!' + else: + print 'Nickname and publicid must be max 16 characters long.' + print 'Secret key must be 40 characters max.\n' + quit() + elif (argv[1][2] == 'l'): + cur.execute('SELECT nickname, publicname FROM oathtokens') + if cur.rowcount != 0: + print " " + str(cur.rowcount) + " keys into database:" + print '[Nickname]\t\t>> [PublicID]' + for i in range(0, cur.rowcount): + (nickname, publicname) = cur.fetchone() + print ' ' + nickname + ' ' * (23-len(nickname)) + ">> " + publicname + print '' + else: + print 'No keys in database\n' + else: + print 'Not enough parameters. Try looking at ' + argv[0] + ' --help' + elif argv[1][0:2] == '-a': + if (argv[1][2] == 'a') and (len(argv)>2): + nickname = re.escape(argv[2]) + cur.execute("SELECT * FROM apikeys WHERE nickname = '" + nickname + "'") + if (cur.rowcount != 0): + print 'API Key for this nickname is already present. Remove it or choose another one.\n' + quit() + cur.execute('SELECT id FROM apikeys ORDER BY id DESC LIMIT 1') + if (cur.rowcount != 0): + id = cur.fetchone()[0] + 1 + else: + id = 1 + api_key = randomChars(20) + cur.execute("INSERT INTO apikeys VALUES ('" + nickname + "', '" + api_key + "', '" + str(id) + "')") + con.commit() + print "New API Key for '" + nickname + "': '" + api_key.encode('base64').strip() + "'" + print "Your API Key ID is: " + str(id) + "\n" + elif (argv[1][2] == 'k') and (len(argv)>2): + nickname = re.escape(argv[2]) + cur.execute("SELECT * FROM apikeys WHERE nickname = '" + nickname + "'") + if (cur.rowcount == 0): + print "API Key for this nickname Doesn't exists!\n" + quit() + cur.execute("DELETE FROM apikeys WHERE nickname = '" + nickname + "'") + con.commit() + print "API Key for '" + nickname + "' has been deleted.\n" + elif (argv[1][2] == 'l'): + cur.execute('SELECT nickname FROM apikeys') + if cur.rowcount != 0: + print ' ' + str(cur.rowcount) + ' keys into database:' + print '[Nickname]' + for i in range(0, cur.rowcount): + nickname = cur.fetchone()[0] + print ' ' + nickname + print '' + else: + print 'No keys in database\n' + \ No newline at end of file diff --git a/src/dump.sql b/src/dump.sql index 859b68b..7cb972e 100644 --- a/src/dump.sql +++ b/src/dump.sql @@ -1,9 +1,25 @@ +BEGIN TRANSACTION; create table yubikeys( - publicname varchar(16) unique not null, - created varchar(24) not null, - internalname varchar(12) not null, - aeskey varchar(32) not null, - active boolean default true, - counter integer not null default 1, - time integer not null default 1 + nickname varchar(16) unique not null, + publicname varchar(16) unique not null, + created varchar(24) not null, + internalname varchar(12) not null, + aeskey varchar(32) not null, + active boolean default true, + counter integer not null default 1, + time integer not null default 1 ); +create table oathtokens( + nickname varchar(16) unique not null, + publicname varchar(12) unique not null, + created varchar(24) not null, + secret varchar(40) not null, + active boolean default true, + counter integer not null default 1 +); +create table apikeys( + nickname varchar(16), + secret varchar(28), + id integer primary key +); +COMMIT; diff --git a/yubikeys.sqlite b/yubikeys.sqlite index c42005b..0d4f75c 100644 Binary files a/yubikeys.sqlite and b/yubikeys.sqlite differ diff --git a/yubiserve-config.php b/yubiserve-config.php deleted file mode 100644 index b5654c2..0000000 --- a/yubiserve-config.php +++ /dev/null @@ -1,28 +0,0 @@ - -Version 1.0: 21/05/2010 - -Licensed under GPL License (see LICENSE file) - -*/ - -$filename = 'yubikeys.sqlite'; - - - -if (!extension_loaded('mcrypt')) { - die("mcrypt not loaded!"); -} - -$logfacility = LOG_LOCAL0; -openlog("yubiserve", LOG_PID, $logfacility) - or die("ERR Syslog open error\n"); - -if (!($db = new SQLiteDatabase($filename))) { - syslog(LOG_INFO, "Cannot access database"); - die("Cannot access database"); -} - -?> diff --git a/yubiserve-utils.php b/yubiserve-utils.php deleted file mode 100644 index 56669a6..0000000 --- a/yubiserve-utils.php +++ /dev/null @@ -1,80 +0,0 @@ - - - -This file is based on the work made by Simon Josefsson . -Follows his license: - -# Copyright (c) 2010 Yubico AB -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -function hex2bin($h) -{ - return pack("H*" , $h); -} - -function modhex2hex($m) -{ - return strtr($m, "cbdefghijklnrtuv", "0123456789abcdef"); -} - -function aes128ecb_decrypt($key,$in) -{ - return bin2hex(mcrypt_ecb(MCRYPT_RIJNDAEL_128, - hex2bin($key), - hex2bin($in), - MCRYPT_DECRYPT, - hex2bin('00000000000000000000000000000000'))); -} - -function calculate_crc($token) -{ - $crc = 0xffff; - - for ($i = 0; $i < 16; $i++ ) { - $b = hexdec($token[$i*2].$token[($i*2)+1]); - $crc = $crc ^ ($b & 0xff); - for ($j = 0; $j < 8; $j++) { - $n = $crc & 1; - $crc = $crc >> 1; - if ($n != 0) { - $crc = $crc ^ 0x8408; - } - } - } - return $crc; -} - -function crc_is_good($token) { - $crc = calculate_crc($token); - return $crc == 0xf0b8; -} - -?> diff --git a/yubiserve.php b/yubiserve.php deleted file mode 100644 index 946a8fa..0000000 --- a/yubiserve.php +++ /dev/null @@ -1,88 +0,0 @@ - -Version 1.0: 21/05/2010 - -Licensed under GPL License (see LICENSE file) - -*/ - -require_once 'yubiserve-config.php'; -require_once 'yubiserve-utils.php'; - -if (!isset($_REQUEST["otp"])) - die ("ERR Missing OTP\n"); -$otp = trim($_REQUEST["otp"]); - -if ((strlen($otp)<32) || (strlen($otp)>48)) { - syslog(LOG_INFO, "Malformed OTP: $otp"); - die ("ERR Malformed OTP\n"); -} - -if (!preg_match("/^([cbdefghijklnrtuv]{0,16})([cbdefghijklnrtuv]{32})$/", $otp, $matches)) { - syslog(LOG_INFO, "Invalid OTP format: $otp"); - die("ERR Invalid OTP format\n"); - } - -$id = $matches[1]; -$modhex_ciphertext = $matches[2]; - -$results = @$db->query("SELECT aeskey, internalname FROM yubikeys WHERE publicname = '$id' AND active = 'true'"); -if ($results === false) { - syslog(LOG_INFO, "Unknown yubikey (internalname: $id)"); - die("ERR Unknown yubikey\n"); -} - -$row = $results->fetchAll(SQLITE_ASSOC); -if (count($row)>1) { - syslog(LOG_INFO, "Multiple keys returned! OTP: $otp"); - die("ERR Malformed OTP"); -} elseif (count($row)<1) { - syslog(LOG_INFO, "Unknown yubikey (internalname: $id)"); - die("ERR Unknown yubikey\n"); -} - -$aeskey = $row[0]['aeskey']; -$internalname = $row[0]['internalname']; - -$ciphertext = modhex2hex($modhex_ciphertext); -$plaintext = aes128ecb_decrypt($aeskey, $ciphertext); - -$uid = substr($plaintext, 0, 12); -if (strcmp($uid, $internalname) != 0) { - syslog(LOG_ERR, "UID error: $otp $plaintext: $uid vs $internalname"); - die("ERR Corrupt OTP\n"); -} - -if (!crc_is_good($plaintext)) { - syslog(LOG_ERR, "CRC error: $otp: $plaintext"); - die("ERR Corrupt OTP\n"); -} - -$counter = substr($plaintext, 14, 2) . substr($plaintext, 12, 2); -$low = substr($plaintext, 18, 2) . substr($plaintext, 16, 2); -$high = substr($plaintext, 20, 2); -$use = substr($plaintext, 22, 2); - -$timestamp = hexdec($high . $low); -$internalcounter = hexdec($counter . $use); - -#print("$counter $use $high $low\n"); - -$results = @$db->query("SELECT counter, time FROM yubikeys WHERE publicname = '$id' AND active = 'true' AND counter < $internalcounter"); -$row = @$results->fetchAll(SQLITE_ASSOC); - -if (count($row)!=1) { - syslog(LOG_ERR, "REPLAYED OTP for yubikey $id (same counter)"); - die("ERR Replayed OTP"); -} elseif ((($row[0] >> 8) == ($internalcounter >> 8)) && ($row[1] <= $timestamp)) { - syslog(LOG_ERR, "REPLAYED OTP for yubikey $id (same timestamp)"); - die("ERR Replayed OTP"); -} - -print "OK Authentication success"; -$results = $db->query("UPDATE yubikeys SET counter = $internalcounter, time = $timestamp WHERE publicname = '$id'"); - -unset($db); -?> diff --git a/yubiserve.png b/yubiserve.png new file mode 100644 index 0000000..ce4c31c Binary files /dev/null and b/yubiserve.png differ -- cgit v1.2.3