diff options
Diffstat (limited to 'passlib/handlers/scram.py')
-rw-r--r-- | passlib/handlers/scram.py | 365 |
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 +#============================================================================= |