199 lines
5.7 KiB
Python
199 lines
5.7 KiB
Python
"""
|
|
module for Yubikey base classes
|
|
"""
|
|
# Copyright (c) 2010, 2011, 2012 Yubico AB
|
|
# See the file COPYING for licence statement.
|
|
|
|
from .yubico_version import __version__
|
|
from . import yubico_exception
|
|
|
|
class YubiKeyError(yubico_exception.YubicoError):
|
|
"""
|
|
Exception raised concerning YubiKey operations.
|
|
|
|
Attributes:
|
|
reason -- explanation of the error
|
|
"""
|
|
def __init__(self, reason='no details'):
|
|
super(YubiKeyError, self).__init__(reason)
|
|
|
|
class YubiKeyTimeout(YubiKeyError):
|
|
"""
|
|
Exception raised when a YubiKey operation timed out.
|
|
|
|
Attributes:
|
|
reason -- explanation of the error
|
|
"""
|
|
def __init__(self, reason='no details'):
|
|
super(YubiKeyTimeout, self).__init__(reason)
|
|
|
|
class YubiKeyVersionError(YubiKeyError):
|
|
"""
|
|
Exception raised when the YubiKey is not capable of something requested.
|
|
|
|
Attributes:
|
|
reason -- explanation of the error
|
|
"""
|
|
def __init__(self, reason='no details'):
|
|
super(YubiKeyVersionError, self).__init__(reason)
|
|
|
|
|
|
class YubiKeyCapabilities(object):
|
|
"""
|
|
Class expressing the functionality of a YubiKey.
|
|
|
|
This base class should be the superset of all sub-classes.
|
|
|
|
In this base class, we lie and say 'yes' to all capabilities.
|
|
|
|
If the base class is used (such as when creating a YubiKeyConfig()
|
|
before getting a YubiKey()), errors must be handled at runtime
|
|
(or later, when the user is unable to use the YubiKey).
|
|
"""
|
|
|
|
model = 'Unknown'
|
|
version = (0, 0, 0,)
|
|
version_num = 0x0
|
|
default_answer = True
|
|
|
|
def __init__(self, model = None, version = None, default_answer = None):
|
|
self.model = model
|
|
if default_answer is not None:
|
|
self.default_answer = default_answer
|
|
if version is not None:
|
|
self.version = version
|
|
(major, minor, build,) = version
|
|
# convert 2.1.3 to 0x00020103
|
|
self.version_num = (major << 24) | (minor << 16) | build
|
|
return None
|
|
|
|
def __repr__(self):
|
|
return '<%s instance at %s: Device %s %s (default: %s)>' % (
|
|
self.__class__.__name__,
|
|
hex(id(self)),
|
|
self.model,
|
|
self.version,
|
|
self.default_answer,
|
|
)
|
|
|
|
def have_yubico_OTP(self):
|
|
return self.default_answer
|
|
|
|
def have_OATH(self, mode):
|
|
return self.default_answer
|
|
|
|
def have_challenge_response(self, mode):
|
|
return self.default_answer
|
|
|
|
def have_serial_number(self):
|
|
return self.default_answer
|
|
|
|
def have_ticket_flag(self, flag):
|
|
return self.default_answer
|
|
|
|
def have_config_flag(self, flag):
|
|
return self.default_answer
|
|
|
|
def have_extended_flag(self, flag):
|
|
return self.default_answer
|
|
|
|
def have_extended_scan_code_mode(self):
|
|
return self.default_answer
|
|
|
|
def have_shifted_1_mode(self):
|
|
return self.default_answer
|
|
|
|
def have_nfc_ndef(self, slot=1):
|
|
return self.default_answer
|
|
|
|
def have_configuration_slot(self):
|
|
return self.default_answer
|
|
|
|
def have_device_config(self):
|
|
return self.default_answer
|
|
|
|
def have_usb_mode(self, mode):
|
|
return self.default_answer
|
|
|
|
def have_scanmap(self):
|
|
return self.default_answer
|
|
|
|
def have_capabilities(self):
|
|
return self.default_answer
|
|
|
|
def have_capability(self, capability):
|
|
return self.default_answer
|
|
|
|
|
|
class YubiKey(object):
|
|
"""
|
|
Base class for accessing YubiKeys
|
|
"""
|
|
|
|
debug = None
|
|
capabilities = None
|
|
|
|
def __init__(self, debug, capabilities = None):
|
|
self.debug = debug
|
|
if capabilities is None:
|
|
self.capabilities = YubiKeyCapabilities(default_answer = False)
|
|
else:
|
|
self.capabilities = capabilities
|
|
return None
|
|
|
|
def version(self):
|
|
""" Get the connected YubiKey's version as a string. """
|
|
pass
|
|
|
|
def serial(self, may_block=True):
|
|
"""
|
|
Get the connected YubiKey's serial number.
|
|
|
|
Note that since version 2.?.? this requires the YubiKey to be
|
|
configured with the extended flag SERIAL_API_VISIBLE.
|
|
|
|
If the YubiKey is configured with SERIAL_BTN_VISIBLE set to True,
|
|
it will start blinking and require a button press before revealing
|
|
the serial number, with a 15 seconds timeout. Set `may_block'
|
|
to False to abort if this is the case.
|
|
"""
|
|
pass
|
|
|
|
def challenge(self, challenge, mode='HMAC', slot=1, variable=True, may_block=True):
|
|
"""
|
|
Get the response to a challenge from a connected YubiKey.
|
|
|
|
`mode' is either 'HMAC' or 'OTP'.
|
|
`slot' is 1 or 2.
|
|
`variable' is only relevant for mode == HMAC.
|
|
|
|
If variable is True, challenge will be padded such that the
|
|
YubiKey will compute the HMAC as if there were no padding.
|
|
If variable is False, challenge will always be NULL-padded
|
|
to 64 bytes.
|
|
|
|
The special case of no input will be HMACed by the YubiKey
|
|
(in variable HMAC mode) as data = 0x00, length = 1.
|
|
|
|
In mode 'OTP', the challenge should be exactly 6 bytes. The
|
|
response will be a YubiKey "ticket" with the 6-byte challenge
|
|
in the ticket.uid field. The rest of the "ticket" will contain
|
|
timestamp and counter information, so two identical challenges
|
|
will NOT result in the same responses. The response is
|
|
decryptable using AES ECB if you have access to the AES key
|
|
programmed into the YubiKey.
|
|
"""
|
|
pass
|
|
|
|
def init_config(self):
|
|
"""
|
|
Return a YubiKey configuration object for this type of YubiKey.
|
|
"""
|
|
pass
|
|
|
|
def write_config(self, cfg, slot):
|
|
"""
|
|
Configure a YubiKey using a configuration object.
|
|
"""
|
|
pass
|