summaryrefslogtreecommitdiff
path: root/passlib/handlers/scram.py
diff options
context:
space:
mode:
Diffstat (limited to 'passlib/handlers/scram.py')
-rw-r--r--passlib/handlers/scram.py365
1 files changed, 182 insertions, 183 deletions
diff --git a/passlib/handlers/scram.py b/passlib/handlers/scram.py
index 2af3980..0dbc823 100644
--- a/passlib/handlers/scram.py
+++ b/passlib/handlers/scram.py
@@ -1,16 +1,16 @@
"""passlib.handlers.scram - hash for SCRAM credential storage"""
-#=========================================================
-#imports
-#=========================================================
-#core
+#=============================================================================
+# imports
+#=============================================================================
+# core
from binascii import hexlify, unhexlify
from base64 import b64encode, b64decode
import hashlib
import re
import logging; log = logging.getLogger(__name__)
from warnings import warn
-#site
-#libs
+# site
+# pkg
from passlib.exc import PasslibHashWarning
from passlib.utils import ab64_decode, ab64_encode, consteq, saslprep, \
to_native_str, xor_bytes, splitcomma
@@ -18,15 +18,14 @@ from passlib.utils.compat import b, bytes, bascii_to_str, iteritems, \
PY3, u, unicode
from passlib.utils.pbkdf2 import pbkdf2, get_prf, norm_hash_name
import passlib.utils.handlers as uh
-#pkg
-#local
+# local
__all__ = [
"scram",
]
-#=========================================================
+#=============================================================================
# scram credentials hash
-#=========================================================
+#=============================================================================
class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
"""This class provides a format for storing SCRAM passwords, and follows
the :ref:`password-hash-api`.
@@ -81,9 +80,9 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
.. automethod:: extract_digest_algs
.. automethod:: derive_digest
"""
- #=========================================================
- #class attrs
- #=========================================================
+ #===================================================================
+ # class attrs
+ #===================================================================
# NOTE: unlike most GenericHandler classes, the 'checksum' attr of
# ScramHandler is actually a map from digest_name -> digest, so
@@ -116,20 +115,20 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
# list of algs verify prefers to use, in order.
_verify_algs = ["sha-256", "sha-512", "sha-224", "sha-384", "sha-1"]
- #=========================================================
+ #===================================================================
# instance attrs
- #=========================================================
+ #===================================================================
# 'checksum' is different from most GenericHandler subclasses,
# in that it contains a dict mapping from alg -> digest,
# or None if no checksum present.
- #: list of algorithms to create/compare digests for.
+ # list of algorithms to create/compare digests for.
algs = None
- #=========================================================
+ #===================================================================
# scram frontend helpers
- #=========================================================
+ #===================================================================
@classmethod
def extract_digest_info(cls, hash, alg):
"""return (salt, rounds, digest) for specific hash algorithm.
@@ -228,9 +227,9 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
alg = norm_hash_name(alg, "hashlib")
return pbkdf2(password, salt, rounds, None, "hmac-" + alg)
- #=========================================================
+ #===================================================================
# serialization
- #=========================================================
+ #===================================================================
@classmethod
def from_string(cls, hash):
@@ -244,7 +243,7 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
# decode rounds
rounds = int(rounds_str)
- if rounds_str != str(rounds): #forbid zero padding, etc.
+ if rounds_str != str(rounds): # forbid zero padding, etc.
raise uh.exc.MalformedHashError(cls)
# decode salt
@@ -292,9 +291,9 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
chk_str = ",".join(self.algs)
return '$scram$%d$%s$%s' % (self.rounds, salt, chk_str)
- #=========================================================
+ #===================================================================
# init
- #=========================================================
+ #===================================================================
def __init__(self, algs=None, **kwds):
super(scram, self).__init__(**kwds)
self.algs = self._norm_algs(algs)
@@ -343,9 +342,9 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
raise ValueError("sha-1 must be in algorithm list of scram hash")
return algs
- #=========================================================
+ #===================================================================
# digest methods
- #=========================================================
+ #===================================================================
@classmethod
def _bind_needs_update(cls, **settings):
@@ -414,164 +413,164 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
# or something went wrong inside _norm_algs()
raise AssertionError("sha-1 digest not found!")
- #=========================================================
+ #===================================================================
#
- #=========================================================
+ #===================================================================
-#=========================================================
+#=============================================================================
# code used for testing scram against protocol examples during development.
-#=========================================================
-#def _test_reference_scram():
-# "quick hack testing scram reference vectors"
-# # NOTE: "n,," is GS2 header - see https://tools.ietf.org/html/rfc5801
-# from passlib.utils.compat import print_
-#
-# engine = _scram_engine(
-# alg="sha-1",
-# salt='QSXCR+Q6sek8bf92'.decode("base64"),
-# rounds=4096,
-# password=u("pencil"),
-# )
-# print_(engine.digest.encode("base64").rstrip())
-#
-# msg = engine.format_auth_msg(
-# username="user",
-# client_nonce = "fyko+d2lbbFgONRv9qkxdawL",
-# server_nonce = "3rfcNHYJY1ZVvWVs7j",
-# header='c=biws',
-# )
-#
-# cp = engine.get_encoded_client_proof(msg)
-# assert cp == "v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=", cp
-#
-# ss = engine.get_encoded_server_sig(msg)
-# assert ss == "rmF9pqV8S7suAoZWja4dJRkFsKQ=", ss
-#
-#class _scram_engine(object):
-# """helper class for verifying scram hash behavior
-# against SCRAM protocol examples. not officially part of Passlib.
-#
-# takes in alg, salt, rounds, and a digest or password.
-#
-# can calculate the various keys & messages of the scram protocol.
-#
-# """
-# #=========================================================
-# # init
-# #=========================================================
-#
-# @classmethod
-# def from_string(cls, hash, alg):
-# "create record from scram hash, for given alg"
-# return cls(alg, *scram.extract_digest_info(hash, alg))
-#
-# def __init__(self, alg, salt, rounds, digest=None, password=None):
-# self.alg = norm_hash_name(alg)
-# self.salt = salt
-# self.rounds = rounds
-# self.password = password
-# if password:
-# data = scram.derive_digest(password, salt, rounds, alg)
-# if digest and data != digest:
-# raise ValueError("password doesn't match digest")
-# else:
-# digest = data
-# elif not digest:
-# raise TypeError("must provide password or digest")
-# self.digest = digest
-#
-# #=========================================================
-# # frontend methods
-# #=========================================================
-# def get_hash(self, data):
-# "return hash of raw data"
-# return hashlib.new(iana_to_hashlib(self.alg), data).digest()
-#
-# def get_client_proof(self, msg):
-# "return client proof of specified auth msg text"
-# return xor_bytes(self.client_key, self.get_client_sig(msg))
-#
-# def get_encoded_client_proof(self, msg):
-# return self.get_client_proof(msg).encode("base64").rstrip()
-#
-# def get_client_sig(self, msg):
-# "return client signature of specified auth msg text"
-# return self.get_hmac(self.stored_key, msg)
-#
-# def get_server_sig(self, msg):
-# "return server signature of specified auth msg text"
-# return self.get_hmac(self.server_key, msg)
-#
-# def get_encoded_server_sig(self, msg):
-# return self.get_server_sig(msg).encode("base64").rstrip()
-#
-# def format_server_response(self, client_nonce, server_nonce):
-# return 'r={client_nonce}{server_nonce},s={salt},i={rounds}'.format(
-# client_nonce=client_nonce,
-# server_nonce=server_nonce,
-# rounds=self.rounds,
-# salt=self.encoded_salt,
-# )
-#
-# def format_auth_msg(self, username, client_nonce, server_nonce,
-# header='c=biws'):
-# return (
-# 'n={username},r={client_nonce}'
-# ','
-# 'r={client_nonce}{server_nonce},s={salt},i={rounds}'
-# ','
-# '{header},r={client_nonce}{server_nonce}'
-# ).format(
-# username=username,
-# client_nonce=client_nonce,
-# server_nonce=server_nonce,
-# salt=self.encoded_salt,
-# rounds=self.rounds,
-# header=header,
-# )
-#
-# #=========================================================
-# # helpers to calculate & cache constant data
-# #=========================================================
-# def _calc_get_hmac(self):
-# return get_prf("hmac-" + iana_to_hashlib(self.alg))[0]
-#
-# def _calc_client_key(self):
-# return self.get_hmac(self.digest, b("Client Key"))
-#
-# def _calc_stored_key(self):
-# return self.get_hash(self.client_key)
-#
-# def _calc_server_key(self):
-# return self.get_hmac(self.digest, b("Server Key"))
-#
-# def _calc_encoded_salt(self):
-# return self.salt.encode("base64").rstrip()
-#
-# #=========================================================
-# # hacks for calculated attributes
-# #=========================================================
-#
-# def __getattr__(self, attr):
-# if not attr.startswith("_"):
-# f = getattr(self, "_calc_" + attr, None)
-# if f:
-# value = f()
-# setattr(self, attr, value)
-# return value
-# raise AttributeError("attribute not found")
-#
-# def __dir__(self):
-# cdir = dir(self.__class__)
-# attrs = set(cdir)
-# attrs.update(self.__dict__)
-# attrs.update(attr[6:] for attr in cdir
-# if attr.startswith("_calc_"))
-# return sorted(attrs)
-# #=========================================================
-# # eoc
-# #=========================================================
-
-#=========================================================
-#eof
-#=========================================================
+#=============================================================================
+##def _test_reference_scram():
+## "quick hack testing scram reference vectors"
+## # NOTE: "n,," is GS2 header - see https://tools.ietf.org/html/rfc5801
+## from passlib.utils.compat import print_
+##
+## engine = _scram_engine(
+## alg="sha-1",
+## salt='QSXCR+Q6sek8bf92'.decode("base64"),
+## rounds=4096,
+## password=u("pencil"),
+## )
+## print_(engine.digest.encode("base64").rstrip())
+##
+## msg = engine.format_auth_msg(
+## username="user",
+## client_nonce = "fyko+d2lbbFgONRv9qkxdawL",
+## server_nonce = "3rfcNHYJY1ZVvWVs7j",
+## header='c=biws',
+## )
+##
+## cp = engine.get_encoded_client_proof(msg)
+## assert cp == "v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=", cp
+##
+## ss = engine.get_encoded_server_sig(msg)
+## assert ss == "rmF9pqV8S7suAoZWja4dJRkFsKQ=", ss
+##
+##class _scram_engine(object):
+## """helper class for verifying scram hash behavior
+## against SCRAM protocol examples. not officially part of Passlib.
+##
+## takes in alg, salt, rounds, and a digest or password.
+##
+## can calculate the various keys & messages of the scram protocol.
+##
+## """
+## #=========================================================
+## # init
+## #=========================================================
+##
+## @classmethod
+## def from_string(cls, hash, alg):
+## "create record from scram hash, for given alg"
+## return cls(alg, *scram.extract_digest_info(hash, alg))
+##
+## def __init__(self, alg, salt, rounds, digest=None, password=None):
+## self.alg = norm_hash_name(alg)
+## self.salt = salt
+## self.rounds = rounds
+## self.password = password
+## if password:
+## data = scram.derive_digest(password, salt, rounds, alg)
+## if digest and data != digest:
+## raise ValueError("password doesn't match digest")
+## else:
+## digest = data
+## elif not digest:
+## raise TypeError("must provide password or digest")
+## self.digest = digest
+##
+## #=========================================================
+## # frontend methods
+## #=========================================================
+## def get_hash(self, data):
+## "return hash of raw data"
+## return hashlib.new(iana_to_hashlib(self.alg), data).digest()
+##
+## def get_client_proof(self, msg):
+## "return client proof of specified auth msg text"
+## return xor_bytes(self.client_key, self.get_client_sig(msg))
+##
+## def get_encoded_client_proof(self, msg):
+## return self.get_client_proof(msg).encode("base64").rstrip()
+##
+## def get_client_sig(self, msg):
+## "return client signature of specified auth msg text"
+## return self.get_hmac(self.stored_key, msg)
+##
+## def get_server_sig(self, msg):
+## "return server signature of specified auth msg text"
+## return self.get_hmac(self.server_key, msg)
+##
+## def get_encoded_server_sig(self, msg):
+## return self.get_server_sig(msg).encode("base64").rstrip()
+##
+## def format_server_response(self, client_nonce, server_nonce):
+## return 'r={client_nonce}{server_nonce},s={salt},i={rounds}'.format(
+## client_nonce=client_nonce,
+## server_nonce=server_nonce,
+## rounds=self.rounds,
+## salt=self.encoded_salt,
+## )
+##
+## def format_auth_msg(self, username, client_nonce, server_nonce,
+## header='c=biws'):
+## return (
+## 'n={username},r={client_nonce}'
+## ','
+## 'r={client_nonce}{server_nonce},s={salt},i={rounds}'
+## ','
+## '{header},r={client_nonce}{server_nonce}'
+## ).format(
+## username=username,
+## client_nonce=client_nonce,
+## server_nonce=server_nonce,
+## salt=self.encoded_salt,
+## rounds=self.rounds,
+## header=header,
+## )
+##
+## #=========================================================
+## # helpers to calculate & cache constant data
+## #=========================================================
+## def _calc_get_hmac(self):
+## return get_prf("hmac-" + iana_to_hashlib(self.alg))[0]
+##
+## def _calc_client_key(self):
+## return self.get_hmac(self.digest, b("Client Key"))
+##
+## def _calc_stored_key(self):
+## return self.get_hash(self.client_key)
+##
+## def _calc_server_key(self):
+## return self.get_hmac(self.digest, b("Server Key"))
+##
+## def _calc_encoded_salt(self):
+## return self.salt.encode("base64").rstrip()
+##
+## #=========================================================
+## # hacks for calculated attributes
+## #=========================================================
+##
+## def __getattr__(self, attr):
+## if not attr.startswith("_"):
+## f = getattr(self, "_calc_" + attr, None)
+## if f:
+## value = f()
+## setattr(self, attr, value)
+## return value
+## raise AttributeError("attribute not found")
+##
+## def __dir__(self):
+## cdir = dir(self.__class__)
+## attrs = set(cdir)
+## attrs.update(self.__dict__)
+## attrs.update(attr[6:] for attr in cdir
+## if attr.startswith("_calc_"))
+## return sorted(attrs)
+## #=========================================================
+## # eoc
+## #=========================================================
+
+#=============================================================================
+# eof
+#=============================================================================