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.
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
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.
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/
Lorsque vous téléchargez le fichier zip, lambda_function.py
a disparu, alors créez à nouveau lambda_function.py
et écrivez lambda_handler
.
Recommended Posts