Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion mig/shared/pwcrypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#
# --- BEGIN_HEADER ---
#
# pwcrypto - helpers for password and crypto including for encryption and hashing

Check warning on line 6 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)

Check warning on line 6 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)
# Copyright (C) 2003-2025 The MiG Project by the Science HPC Center at UCPH
#
# This file is part of MiG.
Expand Down Expand Up @@ -32,7 +32,7 @@
from __future__ import absolute_import

from builtins import zip, range
from base64 import b64encode, b64decode, b16encode, b16decode, binascii, \

Check failure on line 35 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Module "base64" has no attribute "binascii" [attr-defined]

Check failure on line 35 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Module "base64" has no attribute "binascii" [attr-defined]
urlsafe_b64encode, urlsafe_b64decode
from os import urandom
from random import SystemRandom
Expand All @@ -40,8 +40,9 @@
import datetime
import hashlib
import time
import sys

from mig.shared.base import force_utf8, force_native_str, mask_creds, string_snippet

Check warning on line 45 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (84 > 80 characters)

Check warning on line 45 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (84 > 80 characters)
from mig.shared.defaults import keyword_auto, RESET_TOKEN_TTL


Expand All @@ -54,18 +55,18 @@
import cryptography
except ImportError:
# Optional cryptography not available - fail gracefully and check before use
cryptography = None

Check failure on line 58 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Incompatible types in assignment (expression has type "None", variable has type Module) [assignment]

Check failure on line 58 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Incompatible types in assignment (expression has type "None", variable has type Module) [assignment]
# IMPORTANT: some cryptography.X packages require a relatively recent version
try:
from cryptography.fernet import Fernet
except ImportError:
# Optional Fernet not available - fail gracefully and check before use
Fernet = None

Check failure on line 64 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Incompatible types in assignment (expression has type "None", variable has type "type[Fernet]") [assignment]

Check failure on line 64 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Cannot assign to a type [misc]

Check failure on line 64 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Incompatible types in assignment (expression has type "None", variable has type "type[Fernet]") [assignment]

Check failure on line 64 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Cannot assign to a type [misc]
try:
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
except ImportError:
# Optional AESGCM not available - fail gracefully and check before use
AESGCM = None

Check failure on line 69 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Incompatible types in assignment (expression has type "None", variable has type "type[AESGCM]") [assignment]

Check failure on line 69 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Cannot assign to a type [misc]

Check failure on line 69 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Incompatible types in assignment (expression has type "None", variable has type "type[AESGCM]") [assignment]

Check failure on line 69 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Cannot assign to a type [misc]

from mig.shared.defaults import POLICY_NONE, POLICY_WEAK, POLICY_MEDIUM, \
POLICY_HIGH, POLICY_MODERN, POLICY_CUSTOM, PASSWORD_POLICIES
Expand Down Expand Up @@ -372,7 +373,7 @@
invocation.
"""
_logger = configuration.logger
if cryptography and Fernet:

Check failure on line 376 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Function "Fernet" could always be true in boolean context [truthy-function]

Check failure on line 376 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Function "Fernet" could always be true in boolean context [truthy-function]
key = prepare_fernet_key(configuration, secret)
password = force_utf8(password)
fernet_helper = Fernet(key)
Expand All @@ -386,7 +387,7 @@
def fernet_decrypt_password(configuration, encrypted, secret=keyword_auto):
"""Decrypt Fernet encrypted password"""
_logger = configuration.logger
if cryptography and Fernet:

Check failure on line 390 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Function "Fernet" could always be true in boolean context [truthy-function]

Check failure on line 390 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Function "Fernet" could always be true in boolean context [truthy-function]
key = prepare_fernet_key(configuration, secret)
encrypted = force_utf8(encrypted)
fernet_helper = Fernet(key)
Expand Down Expand Up @@ -433,7 +434,7 @@


def _aesgcm_aad_helper(prefix, date_format=AAD_DEFAULT_STAMP, size=32):
"""Use a simple date as counter to keep AES authentication static for a while.

Check warning on line 437 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (82 > 80 characters)

Check warning on line 437 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (82 > 80 characters)
The given date_format fields decide for how long by e.g. adding or removing
the highest resolution fields. By default the output for a single input
value remains constant for the rest of the day. One can adjust it to remain
Expand Down Expand Up @@ -507,7 +508,7 @@
_logger = configuration.logger
# Based on complete example of securely encrypting with AES GCM from
# https://cryptography.io/en/latest/hazmat/primitives/symmetric-encryption
if cryptography and AESGCM:

Check failure on line 511 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Function "AESGCM" could always be true in boolean context [truthy-function]

Check failure on line 511 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Function "AESGCM" could always be true in boolean context [truthy-function]
init_vector = prepare_aesgcm_iv(configuration, init_vector)
auth_data = prepare_aesgcm_aad(configuration, AAD_PREFIX, aad_stamp)
password = force_utf8(password)
Expand All @@ -533,7 +534,7 @@
_logger = configuration.logger
# Based on complete example of securely decrypting with AES GCM from
# https://cryptography.io/en/latest/hazmat/primitives/symmetric-encryption
if cryptography and AESGCM:

Check failure on line 537 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Function "AESGCM" could always be true in boolean context [truthy-function]

Check failure on line 537 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Function "AESGCM" could always be true in boolean context [truthy-function]
(iv, crypt, aad) = __aesgcm_unpack_tuple(encrypted, base64_enc)
if init_vector == keyword_auto:
init_vector = iv
Expand Down Expand Up @@ -630,7 +631,7 @@
for user_dict. Raises ValueError if not.
"""
_logger = configuration.logger
if not 'password_hash' in user_dict and not 'password' in user_dict:

Check warning on line 634 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

test for membership should be 'not in'

Check warning on line 634 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

test for membership should be 'not in'
_logger.error("cannot generate %s reset token for %s without password"
% (auth_type, user_dict['email']))
raise ValueError("No saved password info for %r !" %
Expand Down Expand Up @@ -847,9 +848,9 @@
return min_len, min_classes


def __assure_password_strength_helper(configuration, password, use_legacy=False):

Check warning on line 851 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)

Check warning on line 851 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)
"""Helper to check if password fits site password policy or password legacy
policy in terms of length and required number of different character classes.

Check warning on line 853 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)

Check warning on line 853 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)
We split into four classes for now, lowercase, uppercase, digits and other.
The optional use_legacy argument is used to decide if the configured normal
password policy or any configured password legacy policy should apply.
Expand All @@ -875,11 +876,11 @@
pw_classes.append('other')
continue
for (char_class, values) in list(char_class_map.items()):
if i in "%s" % values and not char_class in pw_classes:

Check warning on line 879 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

test for membership should be 'not in'

Check warning on line 879 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

test for membership should be 'not in'
pw_classes.append(char_class)
break
if len(pw_classes) < min_classes:
raise ValueError('%s: password too simple, >= %d char classes required' %

Check warning on line 883 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)

Check warning on line 883 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)
(policy_fail_msg, min_classes))
if configuration.site_password_cracklib:
if cracklib:
Expand Down Expand Up @@ -953,7 +954,7 @@
otherwise.
"""
# NOTE: hashlib functions require bytes and hexdigest returns native string
if not algo in valid_hash_algos:

Check warning on line 957 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

test for membership should be 'not in'

Check warning on line 957 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

test for membership should be 'not in'
algo = default_algo
hash_helper = valid_hash_algos[algo]
if hex_format:
Expand Down Expand Up @@ -1027,7 +1028,8 @@
raise ValueError("Failed to generate suitable password!")


if __name__ == "__main__":
def main(_exit=sys.exit, _print=print):
"""Run module self-tests"""
from mig.shared.conf import get_configuration_object
configuration = get_configuration_object()
dummy_user = {'distinguished_name': 'Test User', 'password_hash': ''}
Expand Down Expand Up @@ -1057,6 +1059,8 @@
hashed = make_hash(pw)
snippet = string_snippet(hashed)
dummy_user['password_hash'] = hashed
if 'migoid' not in configuration.site_login_methods:
configuration.site_login_methods.append('migoid')
token = generate_reset_token(configuration, dummy_user, 'migoid')
print("Password %r gives hash %r, snippet %r and reset token %r" %
(pw, hashed, snippet, token))
Expand Down Expand Up @@ -1105,10 +1109,14 @@
# print("Password %r static encrypted to %s and decrypted to %r" %
# (pw, encrypted, decrypted))
if pw != decrypted:
raise ValueError("Password static enc+dec corruption: %r vs %r" %

Check warning on line 1112 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)

Check warning on line 1112 in mig/shared/pwcrypto.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)
(pw, decrypted))
print("Password %r aesgcm-static encrypted and decrypted correctly (%r)" %
(pw, encrypted))
except Exception as exc:
print(
"Failed to handle aesgcm static encrypt/decrypt %s : %s" % (pw, exc))


if __name__ == "__main__":
main()
Loading