188 lines
9.0 KiB
Plaintext
188 lines
9.0 KiB
Plaintext
$OpenBSD: patch-yubiserve_py,v 1.8 2018/06/02 12:01:59 jasper 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 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:
|
|
@@ -264,7 +274,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()
|
|
else:
|
|
@@ -285,7 +295,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 +315,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 +332,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()
|