Configuration de l'authentification Digest à l'aide de Python @Lambda

introduction

J'ai recherché l'authentification Digest et trouvé peu d'informations, donc je l'ai écrite comme suite à Paramètres d'authentification de base utilisant Python @ Lambda. J'ai à peine touché à l'authentification Digest elle-même, et elle sert également d'étude du mécanisme.

code

import os
import ctypes
import json
import base64
import time
import hashlib
import copy
from Crypto.Cipher import AES

accounts = [
    {
        "user": "user1",
        "pass": "pass1"
    },
    {
        "user": "user2",
        "pass": "pass2"
    }
    ]

realm = "[email protected]"
qop = "auth"
#Contrairement à l'authentification de base, vous pouvez définir un délai après l'authentification, je le mets donc
timeout = 30 * (10 ** 9) # 30 seconds
#Préparation des informations à utiliser avec le cryptage AES
raw_key = "password1234567890"
raw_iv = "12345678"
key = hashlib.sha256(raw_key.encode()).digest()
iv = hashlib.md5(raw_iv.encode()).digest()

def lambda_handler(event, context):
    request = event.get("Records")[0].get("cf").get("request")
    
    if not check_authorization_header(request):
        return {
            'headers': {
                'www-authenticate': [
                    {
                        'key': 'WWW-Authenticate',
                        'value': create_digest_header()
                    }
                ]
            },
            'status': 401,
            'body': 'Unauthorized'
        }
            
        
    return request

def check_authorization_header(request: dict) -> bool:
    headers = request.get("headers")
    authorization_header = headers.get("authorization")
    
    if not authorization_header:
        return False
    
    data = {
        "method": request.get("method"),
        "uri": request.get("uri")
    }
    header_value = authorization_header[0].get("value")
    #Les données d'authentification Digest sont au format "Digest ~", supprimez donc d'abord les parties inutiles
    header_value = header_value[len("Digest "):]
    
    #Chaque valeur est séparée par une virgule, donc divisez-la
    values = header_value.split(",")
    data = {
        "method": request.get("method"),
        "uri": request.get("uri")
    }
    #Divisez à nouveau chaque valeur pour faciliter la gestion
    for v in values:
        #Puisque nonce est encodé en Base64, il est simplement`=`Si vous le divisez avec, ce sera étrange, alors nous avons affaire à cela
        idx = v.find("=")
        vv = [v[0:idx], v[idx+1:]]
        #Puisqu'il y a un espace demi-largeur avant et après, supprimez-le
        vv[0] = vv[0].strip()
        vv[1] = vv[1].strip()
        #Certaines valeurs sont placées entre guillemets, supprimez-les.
        if vv[1].startswith("\""):
            vv[1] = vv[1][1:]
        if vv[1].endswith("\""):
            vv[1] = vv[1][:len(vv[1])-1]
            
        data[vv[0]] = vv[1]

    for account in accounts:
        if account.get("user") != data.get("username"):
            continue
        
        d = copy.deepcopy(data)
        d["user"] = account.get("user")
        d["pass"] = account.get("pass")
        
        encoded_value = create_validation_data(d)

        if d.get("response") == encoded_value:
            if check_timeout(data.get("nonce")):
                return True

    return False

def check_timeout(nonce: str) -> bool:
    aes = AES.new(key, AES.MODE_CBC, iv)
    value = aes.decrypt(base64.b64decode(nonce.encode())).decode()
    #Avec remplissage lors du cryptage avec AES`_`Est ajouté, alors supprimez ce montant
    while value.endswith("_"):
        value = value[:len(value)-1]

    return int(value) + timeout > time.time_ns()
    
def create_validation_data(data: dict) -> str:
    v1 = "{}:{}:{}".format(data.get("user"), realm, data.get("pass"))
    vv1 = hashlib.md5(v1.encode()).hexdigest()
    v2 = "{}:{}".format(data.get("method"), data.get("uri"))
    vv2 = hashlib.md5(v2.encode()).hexdigest()
    
    v3 = "{}:{}:{}:{}:{}:{}".format(vv1, data.get("nonce"), data.get("nc"), data.get("cnonce"), qop, vv2)

    return hashlib.md5(v3.encode()).hexdigest()

def create_digest_header() -> str:
    aes = AES.new(key, AES.MODE_CBC, iv)
    timestamp = "{}".format(time.time_ns()).encode()
    #Étant donné que la longueur doit être un multiple de 16 lors du cryptage, elle est remplie de remplissage
    while len(timestamp) % 16 != 0:
        timestamp += "_".encode()
        
    header = "Digest "
    values = {
        "realm": '"' + realm + '"',
        "qop": '"auth,auth-int"',
        "algorithm": 'MD5',
        "nonce": '"' + base64.b64encode(aes.encrypt(timestamp)).decode() + '"'
    }
    
    idx = 0
    for k, v in values.items():
        if idx != 0:
            header += ","
        header += '{}={}'.format(k, v)
        idx += 1
        
    return header

Prêt à bouger

Les paramètres pour Lambda et CloudFront sont les mêmes que pour l'authentification de base, il n'y a donc rien à décrire. Cependant, la bibliothèque de chiffrement AES doit être installée avec pip, donc un peu d'assistance est nécessaire.

Faire de la bibliothèque un fichier zip

Puisque pip ne peut pas être exécuté sur Lambda, il est nécessaire de télécharger pip install sur un PC local sous forme de fichier zip.

Une chose à garder à l'esprit pour le moment est que le système d'exploitation d'AWS Lambda est Amazon Linux. Notez que même si vous créez un zip sur votre Mac local, il ne fonctionnera pas, en disant "Oh, je devrais simplement le compresser."

Vous pouvez créer EC2 pour Amazon Linux et créer un fichier zip, mais Docker suffit car vous ne créez au maximum qu'un fichier zip. Je l'ai donc créé en utilisant Docker.

#Extraire et lancer l'image Amazon Linux 2
$ docker run -it amazonlinux:2 bash
#Installez les packages requis sur l'image Docker
$ yum install -y gcc python3 pip3 python3-devel.x86_64
#Installer des packages à utiliser sur Lambda
$ pip install pycrypto -t ./
#Créer un fichier zip
$ zip -r pycrypto.zip Crypto/

Création de lambda_handler

Lorsque vous téléchargez le fichier zip, lambda_function.py a disparu, alors créez à nouveau lambda_function.py et écrivez lambda_handler.

Autres sites référencés

Recommended Posts

Configuration de l'authentification Digest à l'aide de Python @Lambda
Configuration de l'authentification de base à l'aide de Python @Lambda
Récapitulatif si vous utilisez AWS Lambda (Python)
Authentification à l'aide de l'authentification des utilisateurs tweepy et de l'authentification d'application (Python)
[AWS] Utilisation de fichiers ini avec Lambda [Python]
[Python] Accélération du traitement à l'aide des outils de cache
Installez la bibliothèque python sur Lambda à l'aide de [/ tmp]
Remarques sur la configuration d'un conteneur Docker pour l'utilisation de JUMAN ++, KNP, python
Méfiez-vous des disable_existing_loggers lors de la configuration de la journalisation Python
Obtenez une authentification de base avec CloudFront Lambda @ Edge avec Python 3.8
Commencez à utiliser Python
Authentification Python Https
Scraping à l'aide de Python
Comment configurer un environnement Python à l'aide de pyenv
Via un proxy d'authentification pour la communication à l'aide de python urllib3
Manipuler Redmine à l'aide de Python Redmine
Séquence de Fibonacci utilisant Python
expression lambda de python ...
Nettoyage des données à l'aide de Python
Utilisation des packages Python #external
Câblage Communication Pi-SPI avec Python
Calcul de l'âge à l'aide de python
Rechercher sur Twitter avec Python
Exemple d'utilisation de lambda
Identification de nom à l'aide de python
Notes sur l'utilisation de sous-processus Python
python Paramètre japonais respectueux de l'environnement
Essayez d'utiliser Tweepy [Python2.7]
Créez rapidement une API avec Python, lambda et API Gateway à l'aide d'AWS SAM