"Fossies" - the Fresh Open Source Software Archive

Member "keystone-17.0.0/keystone/auth/plugins/totp.py" (13 May 2020, 4679 Bytes) of package /linux/misc/openstack/keystone-17.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "totp.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 16.0.1_vs_17.0.0.

    1 # Licensed under the Apache License, Version 2.0 (the "License"); you may
    2 # not use this file except in compliance with the License. You may obtain
    3 # a copy of the License at
    4 #
    5 #      http://www.apache.org/licenses/LICENSE-2.0
    6 #
    7 # Unless required by applicable law or agreed to in writing, software
    8 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    9 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   10 # License for the specific language governing permissions and limitations
   11 # under the License.
   12 
   13 """Time-based One-time Password Algorithm (TOTP) auth plugin.
   14 
   15 TOTP is an algorithm that computes a one-time password from a shared secret
   16 key and the current time.
   17 
   18 TOTP is an implementation of a hash-based message authentication code (HMAC).
   19 It combines a secret key with the current timestamp using a cryptographic hash
   20 function to generate a one-time password. The timestamp typically increases in
   21 30-second intervals, so passwords generated close together in time from the
   22 same secret key will be equal.
   23 """
   24 
   25 import base64
   26 
   27 from cryptography.hazmat.backends import default_backend
   28 from cryptography.hazmat.primitives import hashes
   29 from cryptography.hazmat.primitives.twofactor import totp as crypto_totp
   30 from oslo_log import log
   31 from oslo_utils import timeutils
   32 
   33 from keystone.auth import plugins
   34 from keystone.auth.plugins import base
   35 from keystone.common import provider_api
   36 import keystone.conf
   37 from keystone import exception
   38 from keystone.i18n import _
   39 
   40 
   41 CONF = keystone.conf.CONF
   42 
   43 METHOD_NAME = 'totp'
   44 
   45 LOG = log.getLogger(__name__)
   46 PROVIDERS = provider_api.ProviderAPIs
   47 
   48 
   49 PASSCODE_LENGTH = 6
   50 PASSCODE_TIME_PERIOD = 30
   51 
   52 
   53 def _generate_totp_passcodes(secret, included_previous_windows=0):
   54     """Generate TOTP passcode.
   55 
   56     :param bytes secret: A base32 encoded secret for the TOTP authentication
   57     :returns: totp passcode as bytes
   58     """
   59     if isinstance(secret, str):
   60         # NOTE(dstanek): since this may be coming from the JSON stored in the
   61         # database it may be UTF-8 encoded
   62         secret = secret.encode('utf-8')
   63 
   64     # NOTE(nonameentername): cryptography takes a non base32 encoded value for
   65     # TOTP. Add the correct padding to be able to base32 decode
   66     while len(secret) % 8 != 0:
   67         secret = secret + b'='
   68 
   69     decoded = base64.b32decode(secret)
   70     # NOTE(lhinds) This is marked as #nosec since bandit will see SHA1
   71     # which is marked as insecure. In this instance however, keystone uses
   72     # HMAC-SHA1 when generating the TOTP, which is currently not insecure but
   73     # will still trigger when scanned by bandit.
   74     totp = crypto_totp.TOTP(
   75         decoded, PASSCODE_LENGTH, hashes.SHA1(), PASSCODE_TIME_PERIOD,  # nosec
   76         backend=default_backend())
   77 
   78     passcode_ts = timeutils.utcnow_ts(microsecond=True)
   79     passcodes = [totp.generate(passcode_ts).decode('utf-8')]
   80 
   81     for i in range(included_previous_windows):
   82         # NOTE(adriant): we move back the timestamp the number of seconds in
   83         # PASSCODE_TIME_PERIOD each time.
   84         passcode_ts -= PASSCODE_TIME_PERIOD
   85         passcodes.append(totp.generate(passcode_ts).decode('utf-8'))
   86     return passcodes
   87 
   88 
   89 class TOTP(base.AuthMethodHandler):
   90 
   91     def authenticate(self, auth_payload):
   92         """Try to authenticate using TOTP."""
   93         response_data = {}
   94         user_info = plugins.TOTPUserInfo.create(auth_payload, METHOD_NAME)
   95         auth_passcode = auth_payload.get('user').get('passcode')
   96 
   97         credentials = PROVIDERS.credential_api.list_credentials_for_user(
   98             user_info.user_id, type='totp')
   99 
  100         valid_passcode = False
  101         for credential in credentials:
  102             try:
  103                 generated_passcodes = _generate_totp_passcodes(
  104                     credential['blob'], CONF.totp.included_previous_windows)
  105                 if auth_passcode in generated_passcodes:
  106                     valid_passcode = True
  107                     break
  108             except (ValueError, KeyError):
  109                 LOG.debug('No TOTP match; credential id: %s, user_id: %s',
  110                           credential['id'], user_info.user_id)
  111             except (TypeError):
  112                 LOG.debug('Base32 decode failed for TOTP credential %s',
  113                           credential['id'])
  114 
  115         if not valid_passcode:
  116             # authentication failed because of invalid username or passcode
  117             msg = _('Invalid username or TOTP passcode')
  118             raise exception.Unauthorized(msg)
  119 
  120         response_data['user_id'] = user_info.user_id
  121 
  122         return base.AuthHandlerResponse(status=True, response_body=None,
  123                                         response_data=response_data)