Replace (unmaintained py2-only) yubiserve with "yubikeyedup", a rewrite.

Note, while it is broadly compatible with the most common use of
yubiserve it has its limits: it supports only sqlite3 (using the same
schema as before) not other databases, and it only supports HTTP
internally, if you require HTTPS then you will need to use a proxy (e.g.
relayd or nginx). It no longer uses a configuration file, only command
line arguments.
This commit is contained in:
sthen 2021-03-29 12:08:39 +00:00
parent 950bbfcaf8
commit 8fe8557fa6
17 changed files with 222 additions and 603 deletions

View File

@ -1,11 +1,11 @@
# $OpenBSD: Makefile,v 1.1238 2021/03/23 08:26:23 sebastia Exp $
# $OpenBSD: Makefile,v 1.1239 2021/03/29 12:08:39 sthen Exp $
COMMENT = exceptions to pkg_add rules
CATEGORIES = devel databases
DISTFILES =
# API.rev
PKGNAME = quirks-3.626
PKGNAME = quirks-3.627
PKG_ARCH = *
MAINTAINER = Marc Espie <espie@openbsd.org>

View File

@ -1,7 +1,7 @@
#! /usr/bin/perl
# ex:ts=8 sw=4:
# $OpenBSD: Quirks.pm,v 1.1252 2021/03/23 08:26:23 sebastia Exp $
# $OpenBSD: Quirks.pm,v 1.1253 2021/03/29 12:08:39 sthen Exp $
#
# Copyright (c) 2009 Marc Espie <espie@openbsd.org>
#
@ -619,6 +619,7 @@ my $stem_extensions = {
'py-axolotl' => 'py3-axolotl',
'py-protobuf' => 'py3-protobuf',
'py-http_ece' => 'py3-http_ece',
'yubiserve' => 'yubikeyedup',
};
my $obsolete_reason = {

View File

@ -1,54 +1,34 @@
# $OpenBSD: Makefile,v 1.22 2021/03/14 11:47:04 sthen Exp $
# $OpenBSD: Makefile,v 1.23 2021/03/29 12:08:39 sthen Exp $
COMMENT= standalone Yubikey and OATH/HOTP validation server
DISTNAME= yubico-yubiserve-3.1
REVISION= 14
EXTRACT_SUFX= .zip
UNZIP= unzip -a
PKGNAME= ${DISTNAME:S/yubico-//}
COMMENT= standalone Yubikey validation server
GH_ACCOUNT= scumjr
GH_PROJECT= yubikeyedup
GH_COMMIT= d5044d3fdc10cc59a5213efd53e0f80b2a1096f9
DISTNAME= yubikeyedup-0.20201226
CATEGORIES= security www
HOMEPAGE= https://code.google.com/p/yubico-yubiserve/
MAINTAINER= Stuart Henderson <stu.ports@spacehopper.org>
# GPLv3
PERMIT_PACKAGE= Yes
MASTER_SITES= ${MASTER_SITE_GOOGLECODE:=yubico-yubiserve/}
MODULES= lang/python
MODPY_VERSION = ${MODPY_DEFAULT_VERSION_2}
BUILD_DEPENDS= databases/sqlite3
RUN_DEPENDS= security/py-cryptodome${MODPY_FLAVOR} \
security/py-openssl${MODPY_FLAVOR}
MODPY_SETUPTOOLS= Yes
RUN_DEPENDS= security/py-cryptodome${MODPY_FLAVOR}
NO_BUILD= Yes
NO_TEST= Yes
PKG_ARCH= *
WRKDIST= ${WRKDIR}/yubico-yubiserve
MODPY_ADJ_FILES= yubiserve.py dbconf.py
pre-patch:
perl -pi -e 's,\? ,\t,g' ${WRKSRC}/dbconf.py
echo >> ${WRKSRC}/yubiserve.py
mv ${WRKSRC}/src ${WRKSRC}/yubikeyedup
mv ${WRKSRC}/tools ${WRKSRC}/yubikeyedup/tools
touch ${WRKSRC}/yubikeyedup/{,tools}/__init__.py
do-configure:
@cd ${WRKSRC}; ${SUBST_CMD} -m 555 -c yubiserve.py yubiserve \
dbconf.py yubiserve-dbconf
cd ${WRKSRC}; sqlite3 yubikeys.sqlite3 < src/dump.sqlite
do-install:
${INSTALL_DATA_DIR} ${PREFIX}/share/doc/yubiserve \
${PREFIX}/share/examples/yubiserve
cd ${WRKSRC}; ${INSTALL_DATA} LICENSE README \
src/* ${PREFIX}/share/doc/yubiserve; \
${INSTALL_DATA} yubiserve.cfg yubikeys.sqlite3 \
${PREFIX}/share/examples/yubiserve; \
${INSTALL_SCRIPT} yubiserve-dbconf ${PREFIX}/bin/yubiserve-dbconf; \
${INSTALL_SCRIPT} yubiserve ${PREFIX}/sbin/yubiserve
post-install:
${INSTALL_DATA_DIR} ${PREFIX}/share/doc/yubikeyedup
${INSTALL_DATA} ${WRKSRC}/{LICENSE,README.rst} \
${PREFIX}/share/doc/yubikeyedup
.include <bsd.port.mk>

View File

@ -1,2 +1,2 @@
SHA256 (yubico-yubiserve-3.1.zip) = pE79hfnIvKbwWkvBuJrYChiQwl8Wbg3T+WNj5NrB1wY=
SIZE (yubico-yubiserve-3.1.zip) = 52856
SHA256 (yubikeyedup-0.20201226-d5044d3f.tar.gz) = 90Q4oHSOt8RoxKFYJgqlyg4B8vILNJef5hq5UWAUrFI=
SIZE (yubikeyedup-0.20201226-d5044d3f.tar.gz) = 25412

View File

@ -1,24 +0,0 @@
$OpenBSD: patch-README,v 1.1.1.1 2012/07/18 08:25:07 sthen Exp $
sqlite3 support and doc fix from upstream
http://code.google.com/p/yubico-yubiserve/source/list r39, r45
--- README.orig Wed Jul 18 01:16:24 2012
+++ README Wed Jul 18 01:16:13 2012
@@ -23,6 +23,7 @@ Under Debian, you can run:
apt-get install python python-crypto python-openssl
If you want to add the sqlite support, you should run:
apt-get install python-sqlite
+sqlite3 is also supported, which is included with python in RHEL6.
Or, if you want to add the mysql support, you should run:
apt-get install python-mysqldb
If you chosen the mysql support, you must create a database and create the
@@ -115,7 +116,7 @@ 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=<api key id>'
+the signature through API Key. To use it, just add the 'id=<api key id>'
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:

View File

@ -1,273 +0,0 @@
$OpenBSD: patch-dbconf_py,v 1.1.1.1 2012/07/18 08:25:07 sthen Exp $
sqlite3 support from http://code.google.com/p/yubico-yubiserve/source/list r39
--- dbconf.py.orig Wed Jul 18 01:16:24 2012
+++ dbconf.py Wed Jul 18 01:04:51 2012
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!${MODPY_BIN}
import time, random, re, os
from sys import argv
try:
@@ -6,12 +6,16 @@ try:
except ImportError:
pass
try:
+ import sqlite3
+except ImportError:
+ pass
+try:
import sqlite
except ImportError:
pass
def parseConfigFile(): # Originally I wrote this function to parse PHP configuration files!
- config = open(os.path.dirname(os.path.realpath(__file__)) + '/yubiserve.cfg', 'r').read().splitlines()
+ config = open('${SYSCONFDIR}/yubiserve/yubiserve.cfg', 'r').read().splitlines()
keys = {}
for line in config:
match = re.search('(.*?)=(.*);', line)
@@ -54,14 +58,15 @@ if config['yubiDB'] == 'mysql' and (config['yubiMySQLH
print "Cannot continue without any MySQL configuration.\nPlease read README.\n\n"
quit()
try:
- if config['yubiDB'] == 'sqlite':
- con = sqlite.connect(os.path.dirname(os.path.realpath(__file__)) + '/yubikeys.sqlite')
+ if config['yubiDB'] == 'sqlite3':
+ con = sqlite3.connect('/var/db/yubiserve/yubikeys.sqlite3')
+ elif config['yubiDB'] == 'sqlite':
+ con = sqlite.connect('/var/db/yubiserve/yubikeys.sqlite')
elif config['yubiDB'] == 'mysql':
con = MySQLdb.connect(host=config['yubiMySQLHost'], user=config['yubiMySQLUser'], passwd=config['yubiMySQLPass'], db=config['yubiMySQLName'])
except:
print "There's a problem with the database!\n"
cur = con.cursor()
-
if (len(argv)<2):
print ' == YubiServe Key Management Tool 2.0 ==\n'
print ' -ya <nickname> <publicid> <secretid> <aeskey>\tAdd a new Yubikey'
@@ -84,13 +89,15 @@ 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):
+ cur.execute("SELECT count(nickname) FROM yubikeys WHERE nickname = '" + nickname + "'")
+ rowcount = cur.fetchone();
+ if not rowcount[0]:
print 'Key not found.'
else:
- cur.execute("SELECT * FROM yubikeys WHERE nickname = '" + nickname + "' AND active = '1'")
- if (cur.rowcount == 1):
- cur.execute("UPDATE yubikeys SET active = '1' WHERE nickname = '" + nickname + "'")
+ cur.execute("SELECT count(nickname) FROM yubikeys WHERE nickname = '" + nickname + "' AND active = '1'")
+ rowcount = cur.fetchone();
+ if rowcount[0]:
+ cur.execute("UPDATE yubikeys SET active = '0' WHERE nickname = '" + nickname + "'")
print "Key '" + nickname + "' disabled."
con.commit()
else:
@@ -98,12 +105,14 @@ else:
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):
+ cur.execute("SELECT count(nickname) FROM yubikeys WHERE nickname = '" + nickname + "'")
+ rowcount = cur.fetchone();
+ if not rowcount[0]:
print 'Key not found.'
else:
- cur.execute("SELECT * FROM yubikeys WHERE nickname = '" + nickname + "' AND active = '1'")
- if (cur.rowcount == 1):
+ cur.execute("SELECT count(nickname) FROM yubikeys WHERE nickname = '" + nickname + "' AND active = '0'")
+ rowcount = cur.fetchone();
+ if rowcount[0]:
cur.execute("UPDATE yubikeys SET active = '1' WHERE nickname = '" + nickname + "'")
print "Key '" + nickname + "' enabled."
con.commit()
@@ -111,8 +120,9 @@ 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):
+ cur.execute("SELECT count(nickname) FROM yubikeys WHERE nickname = '" + nickname + "'")
+ rowcount = cur.fetchone();
+ if not rowcount[0]:
print 'Key not found.'
else:
cur.execute("DELETE FROM yubikeys WHERE nickname = '" + nickname + "'")
@@ -121,8 +131,9 @@ else:
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("SELECT count(nickname) FROM yubikeys WHERE nickname = '" + argv[2] + "' OR publicname = '" + argv[3] + "'")
+ rowcount = cur.fetchone();
+ if not 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] + "', 1, 1, 1)")
con.commit()
print "Key '" + argv[2] + "' added to database."
@@ -133,13 +144,14 @@ else:
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, active FROM yubikeys')
- if cur.rowcount != 0:
- print " " + str(cur.rowcount) + " keys into database:"
+ cur.execute('SELECT count(nickname) FROM yubikeys')
+ rowcount = cur.fetchone();
+ print " %s keys into database:" % (rowcount[0])
+ if rowcount[0]:
+ cur.execute('SELECT nickname, publicname, active FROM yubikeys')
print '[Nickname]\t\t>> [PublicID]'
- for i in range(0, cur.rowcount):
- (nickname, publicname, active) = cur.fetchone()
- print ' ' + nickname + ' ' * (23-len(nickname)) + ">> " + publicname + ' ' * (21-len(publicname)) + ">> " + active
+ for (nickname, publicname, active) in cur:
+ print '%-23s >> %-21s >> %s ' % (nickname, publicname, active)
print ''
else:
print 'No keys in database\n'
@@ -148,12 +160,14 @@ else:
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):
+ cur.execute("SELECT count(nickname) FROM oathtokens WHERE nickname = '" + nickname + "'")
+ rowcount = cur.fetchone();
+ if not rowcount[0]:
print 'Key not found.'
else:
- cur.execute("SELECT * FROM oathtokens WHERE nickname = '" + nickname + "' AND active = '1'")
- if (cur.rowcount == 1):
+ cur.execute("SELECT count(nickname) FROM oathtokens WHERE nickname = '" + nickname + "' AND active = '1'")
+ rowcount = cur.fetchone();
+ if rowcount[0]:
cur.execute("UPDATE oathtokens SET active = '1' WHERE nickname = '" + nickname + "'")
print "Key '" + nickname + "' disabled."
con.commit()
@@ -162,12 +176,14 @@ else:
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):
+ cur.execute("SELECT count(nickname) FROM oathtokens WHERE nickname = '" + nickname + "'")
+ rowcount = cur.fetchone();
+ if not rowcount[0]:
print 'Key not found.'
else:
- cur.execute("SELECT * FROM oathtokens WHERE nickname = '" + nickname + "' AND active = '1'")
- if (cur.rowcount == 1):
+ cur.execute("SELECT count(nickname) FROM oathtokens WHERE nickname = '" + nickname + "' AND active = '1'")
+ rowcount = cur.fetchone();
+ if rowcount[0]:
cur.execute("UPDATE oathtokens SET active = '1' WHERE nickname = '" + nickname + "'")
print "Key '" + nickname + "' enabled."
con.commit()
@@ -175,8 +191,9 @@ 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):
+ cur.execute("SELECT count(nickname) FROM oathtokens WHERE nickname = '" + nickname + "'")
+ rowcount = cur.fetchone();
+ if not rowcount[0]:
print 'Key not found.'
else:
cur.execute("DELETE FROM oathtokens WHERE nickname = '" + nickname + "'")
@@ -185,8 +202,9 @@ else:
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("SELECT count(nickname) FROM oathtokens WHERE nickname = '" + argv[2] + "' OR publicname = '" + argv[3] + "'")
+ rowcount = cur.fetchone();
+ if not rowcount[0]:
cur.execute("INSERT INTO oathtokens VALUES ('" + nickname + "', '" + argv[3] + "', '" + time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + "', '" + argv[4] + "', 1, 1)")
con.commit()
print "Key '" + argv[2] + "' added to database."
@@ -197,14 +215,14 @@ else:
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:"
+ cur.execute('SELECT count(nickname) FROM oathtokens')
+ rowcount = cur.fetchone();
+ print " %s keys into database:" % (rowcount[0])
+ if rowcount[0]:
+ cur.execute('SELECT nickname, publicname FROM oathtokens')
print '[Nickname]\t\t>> [PublicID]'
- for i in range(0, cur.rowcount):
- (nickname, publicname) = cur.fetchone()
- print ' ' + nickname + ' ' * (23-len(nickname)) + ">> " + publicname
- print ''
+ for (nickname, publicname) in cur:
+ print '%-23s >> %-21s >> %s ' % (nickname, publicname)
else:
print 'No keys in database\n'
else:
@@ -212,13 +230,15 @@ else:
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):
+ cur.execute("SELECT count(nickname) FROM apikeys WHERE nickname = '" + nickname + "'")
+ rowcount = cur.fetchone();
+ if 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
+ lastid = cur.fetchone()
+ if lastid:
+ id = lastid[0] + 1
else:
id = 1
api_key = randomChars(20)
@@ -228,22 +248,23 @@ else:
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):
+ cur.execute("SELECT count(nickname) FROM apikeys WHERE nickname = '" + nickname + "'")
+ rowcount = cur.fetchone();
+ if not 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:'
+ cur.execute('SELECT count(nickname) FROM apikeys')
+ rowcount = cur.fetchone();
+ print " %s keys into database:" % (rowcount[0])
+ if rowcount[0]:
+ cur.execute('SELECT nickname FROM apikeys')
print '[Nickname]'
- for i in range(0, cur.rowcount):
- nickname = cur.fetchone()[0]
- print ' ' + nickname
- print ''
+ for (nickname) in cur:
+ print '%-23s' % (nickname)
else:
print 'No keys in database\n'
-
\ No newline at end of file
+

View File

@ -0,0 +1,34 @@
$OpenBSD: patch-setup_py,v 1.1 2021/03/29 12:08:39 sthen Exp $
https://github.com/scumjr/yubikeyedup/pull/5
Index: setup.py
--- setup.py.orig
+++ setup.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+
+from setuptools import setup, find_packages
+
+setup(
+ name='yubikeyedup',
+ version='0.1',
+ author="Alessio Periloso, scumjr, jaroug",
+ author_email="mail@periloso.it",
+ license="GPL v3",
+ description="Yet Another YubiKey OTP Validation Server",
+ long_description=open('README.rst').read(),
+
+ packages=find_packages(),
+ include_package_data=True,
+ classifiers=[
+ ],
+
+ entry_points = {
+ 'console_scripts': [
+ 'yubikeyedup=yubikeyedup.yubiserve:main',
+ 'yubikeyedup-dbcreate=yubikeyedup.tools.dbcreate:main',
+ 'yubikeyedup-dbconf=yubikeyedup.tools.dbconf:main',
+ ],
+ },
+)

View File

@ -0,0 +1,21 @@
$OpenBSD: patch-yubikeyedup_tools_dbconf_py,v 1.1 2021/03/29 12:08:39 sthen Exp $
Index: yubikeyedup/tools/dbconf.py
--- yubikeyedup/tools/dbconf.py.orig
+++ yubikeyedup/tools/dbconf.py
@@ -251,7 +251,7 @@ class API(DBConf):
return keys
-if __name__ == '__main__':
+def main():
options = {
'-ya': (4, Yubikey, 'add'),
'-yk': (1, Yubikey, 'delete'),
@@ -290,3 +290,6 @@ if __name__ == '__main__':
function = getattr(klass, fname)
db = klass(filename)
function(db, *args)
+
+if __name__ == '__main__':
+ main()

View File

@ -0,0 +1,19 @@
$OpenBSD: patch-yubikeyedup_tools_dbcreate_py,v 1.1 2021/03/29 12:08:39 sthen Exp $
Index: yubikeyedup/tools/dbcreate.py
--- yubikeyedup/tools/dbcreate.py.orig
+++ yubikeyedup/tools/dbcreate.py
@@ -42,9 +42,12 @@ def create_db(filename):
conn.commit()
conn.close()
-if __name__ == '__main__':
+def main():
if len(sys.argv) != 2:
print('Usage: %s <db.sqlite3>' % sys.argv[0])
sys.exit(0)
create_db(sys.argv[1])
+
+if __name__ == '__main__':
+ main()

View File

@ -0,0 +1,18 @@
$OpenBSD: patch-yubikeyedup_validate_py,v 1.1 2021/03/29 12:08:39 sthen Exp $
Index: yubikeyedup/validate.py
--- yubikeyedup/validate.py.orig
+++ yubikeyedup/validate.py
@@ -1,9 +1,9 @@
import re
-from Cryptodome.Cipher import AES
+from Crypto.Cipher import AES
-from sql import *
-import yubistatus
+from yubikeyedup.sql import *
+from yubikeyedup import yubistatus
class Validate:

View File

@ -0,0 +1,43 @@
$OpenBSD: patch-yubikeyedup_yubiserve_py,v 1.1 2021/03/29 12:08:39 sthen Exp $
Index: yubikeyedup/yubiserve.py
--- yubikeyedup/yubiserve.py.orig
+++ yubikeyedup/yubiserve.py
@@ -16,10 +16,10 @@ import time
import urllib.request, urllib.parse, urllib.error
import urllib.parse
-import yubistatus
-import validate
-import html
-from sql import *
+from yubikeyedup import yubistatus
+from yubikeyedup import validate
+from yubikeyedup import html
+from yubikeyedup.sql import *
class YubiServeHandler:
@@ -134,12 +134,13 @@ def stop_signal_handler(signum, frame):
sys.exit(0)
-if __name__ == '__main__':
+def main():
parser = optparse.OptionParser('Usage: %prog [options]')
parser.add_option('-d', '--db', default='./yubikeys.sqlite3', dest='db')
parser.add_option('-a', '--address', default='0.0.0.0', dest='host')
parser.add_option('-p', '--port', default='8000', dest='port')
(options, args) = parser.parse_args()
+ global sqlite_db
sqlite_db = options.db
yubiserveHTTP = ThreadingHTTPServer((options.host, int(options.port)), YubiHTTPServer)
@@ -151,3 +152,7 @@ if __name__ == '__main__':
http_thread.start()
signal.pause()
+
+
+if __name__ == '__main__':
+ main()

View File

@ -1,17 +0,0 @@
$OpenBSD: patch-yubiserve_cfg,v 1.1.1.1 2012/07/18 08:25:07 sthen Exp $
--- yubiserve.cfg.orig Thu Mar 24 11:25:52 2011
+++ yubiserve.cfg Thu Jun 7 02:48:38 2012
@@ -1,9 +1,10 @@
yubiservePORT = 8000;
yubiserveSSLPORT = 8001;
yubiserveHOST = '0.0.0.0';
-yubiDB = 'sqlite';
+yubiDB = 'sqlite3';
#yubiDB = 'mysql';
#yubiMySQLHost = 'localhost';
#yubiMySQLUser = 'yubiserve';
#yubiMySQLPass = 'yubipass';
-#yubiMySQLName = 'yubikeys';
\ No newline at end of file
+#yubiMySQLName = 'yubikeys';
+yubiserveDebugLevel = 0;

View File

@ -1,197 +0,0 @@
$OpenBSD: patch-yubiserve_py,v 1.9 2019/03/25 01:29:42 sthen Exp $
sqlite3 support from
http://code.google.com/p/yubico-yubiserve/source/list r39
remove bogus timestamp checking code; that isn't what the timestamps are for,
and they wrap after between 0 and 24-and-a-bit days uptime causing failures.
Index: yubiserve.py
--- yubiserve.py.orig
+++ yubiserve.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!${MODPY_BIN}
import re, os, time, socket
import urlparse, SocketServer, urllib, BaseHTTPServer
from Crypto.Cipher import AES
@@ -10,12 +10,16 @@ try:
except ImportError:
pass
try:
+ import sqlite3
+except ImportError:
+ pass
+try:
import sqlite
except ImportError:
pass
def parseConfigFile(): # Originally I wrote this function to parse PHP configuration files!
- config = open(os.path.dirname(os.path.realpath(__file__)) + '/yubiserve.cfg', 'r').read().splitlines()
+ config = open('${SYSCONFDIR}/yubiserve/yubiserve.cfg', 'r').read().splitlines()
keys = {}
for line in config:
match = re.search('(.*?)=(.*);', line)
@@ -45,7 +49,7 @@ class OATHValidation():
def validateOATH(self, OATH, publicID):
cur = self.con.cursor()
cur.execute("SELECT counter, secret FROM oathtokens WHERE publicname = '" + publicID + "' AND active = '1'")
- if (cur.rowcount != 1):
+ if not cur:
validationResult = self.status['BAD_OTP']
return validationResult
(actualcounter, key) = cur.fetchone()
@@ -99,6 +103,7 @@ class OTPValidation():
def getResponse(self):
return self.validationResponse
def validateOTP(self, OTP):
+ global config
self.OTP = re.escape(OTP)
self.validationResult = 0
if (len(OTP) <= 32) or (len(OTP) > 48):
@@ -109,15 +114,21 @@ class OTPValidation():
if match.group(1) and match.group(2):
self.userid = match.group(1)
self.token = self.modhex2hex(match.group(2))
+ # pdb.set_trace()
cur = self.con.cursor()
cur.execute('SELECT aeskey, internalname FROM yubikeys WHERE publicname = "' + self.userid + '" AND active = "1"')
- if (cur.rowcount != 1):
+ if not cur:
+ if config['yubiserveDebugLevel'] > 0:
+ print "Yubikey rejected because it is not found in the database, using the query: 'SELECT aeskey, internalname FROM yubikeys WHERE publicname = \"%s\" AND active = \"1\"'" % (self.userid)
self.validationResult = self.status['BAD_OTP']
return self.validationResult
(self.aeskey, self.internalname) = cur.fetchone()
self.plaintext = self.aes128ecb_decrypt(self.aeskey, self.token)
uid = self.plaintext[:12]
if (self.internalname != uid):
+ if config['yubiserveDebugLevel'] > 0:
+ print "Yubikey rejected because the uid (6 byte secret) in the decrypted AES key (set with with ykpersonalise -ouid) does not match the secret key (internalname) in the database"
+ print "Decrypted AES: %s\n Username from yubikey: %s shoould equal the database username: %s" % (self.plaintext, uid, self.internalname)
self.validationResult = self.status['BAD_OTP']
return self.validationResult
if not (self.CRC() or self.isCRCValid()):
@@ -126,16 +137,13 @@ class OTPValidation():
self.internalcounter = self.hexdec(self.plaintext[14:16] + self.plaintext[12:14] + self.plaintext[22:24])
self.timestamp = self.hexdec(self.plaintext[20:22] + self.plaintext[18:20] + self.plaintext[16:18])
cur.execute('SELECT counter, time FROM yubikeys WHERE publicname = "' + self.userid + '" AND active = "1"')
- if (cur.rowcount != 1):
+ if not cur:
self.validationResult = self.status['BAD_OTP']
return self.validationResult
(self.counter, self.time) = cur.fetchone()
if (self.counter) >= (self.internalcounter):
self.validationResult = self.status['REPLAYED_OTP']
return self.validationResult
- if (self.time >= self.timestamp) and ((self.counter >> 8) == (self.internalcounter >> 8)):
- self.validationResult = self.status['DELAYED_OTP']
- return self.validationResult
except IndexError:
self.validationResult = self.status['BAD_OTP']
return self.validationResult
@@ -147,11 +155,13 @@ class OTPValidation():
class YubiServeHandler (BaseHTTPServer.BaseHTTPRequestHandler):
__base = BaseHTTPServer.BaseHTTPRequestHandler
__base_handle = __base.handle
- server_version = 'Yubiserve/3.0'
+ server_version = 'Yubiserve/3.1'
global config
#try:
- if config['yubiDB'] == 'sqlite':
- con = sqlite.connect(os.path.dirname(os.path.realpath(__file__)) + '/yubikeys.sqlite')
+ if config['yubiDB'] == 'sqlite3':
+ con = sqlite3.connect('/var/db/yubiserve/yubikeys.sqlite3', check_same_thread = False)
+ elif config['yubiDB'] == 'sqlite':
+ con = sqlite.connect('/var/db/yubiserve/yubikeys.sqlite', check_same_thread = False)
elif config['yubiDB'] == 'mysql':
con = MySQLdb.connect(host=config['yubiMySQLHost'], user=config['yubiMySQLUser'], passwd=config['yubiMySQLPass'], db=config['yubiMySQLName'])
#except:
@@ -190,7 +200,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequest
if len(query) > 0:
getData = self.getToDict(query)
otpvalidation = OTPValidation(self.con)
- validation = otpvalidation.validateOTP(getData['otp'])
+ validation = otpvalidation.validateOTP(getData['otp'].lower())
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
@@ -200,16 +210,16 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequest
orderedResult = 'nonce=' + getData['nonce'] + '&otp=' + getData['otp'] + '&sl=100&status=' + [k for k, v in otpvalidation.status.iteritems() if v == validation][0] + '&t=' + iso_time
except KeyError:
result = 't=' + iso_time + '\r\notp=' + getData['otp'] + '\r\nnonce=\r\nsl=100\r\nstatus=' + [k for k, v in otpvalidation.status.iteritems() if v == validation][0] + '\r\n'
- orderedResult = 'nonce=&otp=' + getData['otp'] + 'sl=100&status=' + [k for k, v in otpvalidation.status.iteritems() if v == validation][0] + '&t=' + iso_time
+ orderedResult = 'nonce=&otp=' + getData['otp'] + '&sl=100&status=' + [k for k, v in otpvalidation.status.iteritems() if v == validation][0] + '&t=' + iso_time
otp_hmac = ''
try:
if (getData['id'] != None):
apiID = re.escape(getData['id'])
cur = self.con.cursor()
cur.execute("SELECT secret from apikeys WHERE id = '" + apiID + "'")
- if cur.rowcount != 0:
+ if cur:
api_key = cur.fetchone()[0]
- otp_hmac = hmac.new(api_key, msg=orderedResult, digestmod=hashlib.sha1).hexdigest().decode('hex').encode('base64').strip()
+ otp_hmac = hmac.new(str(api_key), msg=str(orderedResult), digestmod=hashlib.sha1).hexdigest().decode('hex').encode('base64').strip()
else:
result = 't=' + iso_time + '\r\notp=' + getData['otp'] + '\r\nstatus=NO_CLIENT\r\n'
except KeyError:
@@ -230,7 +240,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequest
apiID = re.escape(getData['id'])
cur = self.con.cursor()
cur.execute("SELECT secret from apikeys WHERE id = '" + apiID + "'")
- if cur.rowcount != 0:
+ if cur:
api_key = cur.fetchone()[0]
otp_hmac = hmac.new(api_key, msg=orderedResult, digestmod=hashlib.sha1).hexdigest().decode('hex').encode('base64').strip()
except KeyError:
@@ -258,15 +268,16 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequest
self.end_headers()
iso_time = time.strftime("%Y-%m-%dT%H:%M:%S")
result = 'otp=' + getData['otp'] + '\r\nstatus=' + [k for k, v in oathvalidation.status.iteritems() if v == validation][0] + '\r\nt=' + iso_time
+ orderedResult = 'otp=' + getData['otp'] + '&status=' + [k for k, v in oathvalidation.status.iteritems() if v == validation][0] + '&t=' + iso_time
otp_hmac = ''
try:
if (getData['id'] != None):
apiID = re.escape(getData['id'])
cur = self.con.cursor()
cur.execute("SELECT secret from apikeys WHERE id = '" + apiID + "'")
- if cur.rowcount != 0:
+ if cur:
api_key = cur.fetchone()[0]
- otp_hmac = hmac.new(api_key, msg=result, digestmod=hashlib.sha1).hexdigest().decode('hex').encode('base64').strip()
+ otp_hmac = hmac.new(str(api_key), msg=str(orderedResult), digestmod=hashlib.sha1).hexdigest().decode('hex').encode('base64').strip()
else:
result = 'otp=' + getData['otp'] + '\r\nstatus=NO_CLIENT\r\nt=' + iso_time
except KeyError:
@@ -285,7 +296,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequest
apiID = re.escape(getData['id'])
cur = self.con.cursor()
cur.execute("SELECT secret from apikeys WHERE id = '" + apiID + "'")
- if cur.rowcount != 0:
+ if cur:
api_key = cur.fetchone()[0]
otp_hmac = hmac.new(api_key, msg=result, digestmod=hashlib.sha1).hexdigest().decode('hex').encode('base64').strip()
except KeyError:
@@ -305,7 +316,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequest
apiID = re.escape(getData['id'])
cur = self.con.cursor()
cur.execute("SELECT secret from apikeys WHERE id = '" + apiID + "'")
- if cur.rowcount != 0:
+ if cur:
api_key = cur.fetchone()[0]
otp_hmac = hmac.new(api_key, msg=result, digestmod=hashlib.sha1).hexdigest().decode('hex').encode('base64').strip()
except KeyError:
@@ -322,9 +333,10 @@ class SecureHTTPServer(BaseHTTPServer.HTTPServer):
def __init__(self, server_address, HandlerClass):
BaseHTTPServer.HTTPServer.__init__(self, server_address, HandlerClass)
ctx = SSL.Context(SSL.SSLv23_METHOD)
- fpem = os.path.dirname(os.path.realpath(__file__)) + '/yubiserve.pem'
+ fpem = '${SYSCONFDIR}/yubiserve/yubiserve.pem'
ctx.use_privatekey_file (fpem)
ctx.use_certificate_file(fpem)
+ ctx.use_certificate_chain_file(fpem)
self.socket = SSL.Connection(ctx, socket.socket(self.address_family, self.socket_type))
self.server_bind()
self.server_activate()

View File

@ -1,6 +1,9 @@
YubiServe is a lightweight Validation Server supporting both OATH/HOTP
and Yubico Yubikey implementations, written in Python that uses an
SQLite database or, optionally, a MySQL database. It has an integrated
threaded webserver, with HTTPS/SSL support, compatible with the
Yubico validation protocol 2.0 including HMAC SHA-1 signatures to
provide for authentication of the server.
yubikeyedup is a lightweight validation server for Yubikey OTP
authentication written in Python. It provides the standard HTTP-based
Validation Protocol 2.0 used by various servers including Yubico's
official PHP-based server.
It is a complete rewrite of the earlier project "YubiServe" (seemingly
abandoned and never ported to Python 3). It uses the same database schema
and is broadly compatible, though does not support HTTPS internally (if
wanted, this can be added by proxying from relayd, nginx or similar).

View File

@ -1,32 +1,42 @@
@comment $OpenBSD: PLIST,v 1.1.1.1 2012/07/18 08:25:07 sthen Exp $
@comment $OpenBSD: PLIST,v 1.2 2021/03/29 12:08:39 sthen Exp $
@newgroup _yubiserve:700
@newuser _yubiserve:700:_yubiserve:daemon:yubiserve user:/nonexistent:/sbin/nologin
bin/yubiserve-dbconf
sbin/yubiserve
share/doc/yubiserve/
share/doc/yubiserve/LICENSE
share/doc/yubiserve/README
share/doc/yubiserve/dump.mysql
share/doc/yubiserve/dump.sqlite
share/examples/yubiserve/
share/examples/yubiserve/yubikeys.sqlite3
@mode 770
@owner _yubiserve
@group _yubiserve
@sample /var/db/yubiserve/
@mode 640
@sample /var/db/yubiserve/yubikeys.sqlite3
@mode
@owner
@group
share/examples/yubiserve/yubiserve.cfg
@mode 750
@owner root
@group _yubiserve
@sample ${SYSCONFDIR}/yubiserve/
@mode 640
@sample ${SYSCONFDIR}/yubiserve/yubiserve.cfg
@mode
@owner
@group
@rcscript ${RCDIR}/yubiserve
@rcscript ${RCDIR}/yubikeyedup
bin/yubikeyedup
bin/yubikeyedup-dbconf
bin/yubikeyedup-dbcreate
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/
lib/python${MODPY_VERSION}/site-packages/yubikeyedup-0.1-py${MODPY_VERSION}.egg-info/
lib/python${MODPY_VERSION}/site-packages/yubikeyedup-0.1-py${MODPY_VERSION}.egg-info/PKG-INFO
lib/python${MODPY_VERSION}/site-packages/yubikeyedup-0.1-py${MODPY_VERSION}.egg-info/SOURCES.txt
lib/python${MODPY_VERSION}/site-packages/yubikeyedup-0.1-py${MODPY_VERSION}.egg-info/dependency_links.txt
lib/python${MODPY_VERSION}/site-packages/yubikeyedup-0.1-py${MODPY_VERSION}.egg-info/entry_points.txt
lib/python${MODPY_VERSION}/site-packages/yubikeyedup-0.1-py${MODPY_VERSION}.egg-info/top_level.txt
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/__init__.py
${MODPY_COMMENT}lib/python${MODPY_VERSION}/site-packages/yubikeyedup/${MODPY_PYCACHE}/
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/${MODPY_PYCACHE}__init__.${MODPY_PYC_MAGIC_TAG}pyc
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/${MODPY_PYCACHE}html.${MODPY_PYC_MAGIC_TAG}pyc
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/${MODPY_PYCACHE}sql.${MODPY_PYC_MAGIC_TAG}pyc
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/${MODPY_PYCACHE}validate.${MODPY_PYC_MAGIC_TAG}pyc
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/${MODPY_PYCACHE}yubiserve.${MODPY_PYC_MAGIC_TAG}pyc
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/${MODPY_PYCACHE}yubistatus.${MODPY_PYC_MAGIC_TAG}pyc
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/html.py
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/sql.py
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/tools/
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/tools/__init__.py
${MODPY_COMMENT}lib/python${MODPY_VERSION}/site-packages/yubikeyedup/tools/${MODPY_PYCACHE}/
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/tools/${MODPY_PYCACHE}__init__.${MODPY_PYC_MAGIC_TAG}pyc
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/tools/${MODPY_PYCACHE}dbconf.${MODPY_PYC_MAGIC_TAG}pyc
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/tools/${MODPY_PYCACHE}dbcreate.${MODPY_PYC_MAGIC_TAG}pyc
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/tools/${MODPY_PYCACHE}flash.${MODPY_PYC_MAGIC_TAG}pyc
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/tools/${MODPY_PYCACHE}unittests.${MODPY_PYC_MAGIC_TAG}pyc
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/tools/dbconf.py
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/tools/dbcreate.py
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/tools/flash.py
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/tools/unittests.py
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/validate.py
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/yubiserve.py
lib/python${MODPY_VERSION}/site-packages/yubikeyedup/yubistatus.py
share/doc/yubikeyedup/
share/doc/yubikeyedup/LICENSE
share/doc/yubikeyedup/README.rst

View File

@ -0,0 +1,15 @@
#!/bin/ksh
#
# $OpenBSD: yubikeyedup.rc,v 1.1 2021/03/29 12:08:39 sthen Exp $
daemon="${TRUEPREFIX}/bin/yubikeyedup"
daemon_flags="--db /var/db/yubiserve/yubikeys.sqlite3"
daemon_user="_yubiserve"
. /etc/rc.d/rc.subr
pexp="${MODPY_BIN} ${daemon}${daemon_flags:+ ${daemon_flags}}"
rc_bg=YES
rc_reload=NO
rc_cmd $1

View File

@ -1,14 +0,0 @@
#!/bin/ksh
#
# $OpenBSD: yubiserve.rc,v 1.5 2018/01/11 19:27:09 rpe Exp $
daemon="${TRUEPREFIX}/sbin/yubiserve"
daemon_user="_yubiserve"
. /etc/rc.d/rc.subr
pexp="${MODPY_BIN} ${daemon}${daemon_flags:+ ${daemon_flags}}"
rc_bg=YES
rc_reload=NO
rc_cmd $1