Ich habe nach Digest-Authentifizierung gesucht und nur wenige Informationen gefunden. Daher habe ich sie als Fortsetzung von Grundlegende Authentifizierungseinstellungen mit Python @ Lambda geschrieben. Ich habe die Digest-Authentifizierung selbst kaum berührt und sie dient auch als Untersuchung des Mechanismus.
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"
#Im Gegensatz zur Standardauthentifizierung können Sie nach der Authentifizierung ein Zeitlimit festlegen, also habe ich es eingegeben
timeout = 30 * (10 ** 9) # 30 seconds
#Vorbereiten von Informationen zur Verwendung mit AES-Verschlüsselung
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")
#Digest-Authentifizierungsdaten haben das Format "Digest ~". Löschen Sie daher zuerst unnötige Teile
header_value = header_value[len("Digest "):]
#Jeder Wert wird durch ein Komma getrennt, also teilen Sie ihn
values = header_value.split(",")
data = {
"method": request.get("method"),
"uri": request.get("uri")
}
#Teilen Sie jeden Wert erneut, um die Handhabung zu vereinfachen
for v in values:
#Da nonce Base64-codiert ist, ist es einfach`=`Wenn Sie es mit teilen, wird es seltsam sein, also beschäftigen wir uns damit
idx = v.find("=")
vv = [v[0:idx], v[idx+1:]]
#Da vorher und nachher ein Leerzeichen mit halber Breite vorhanden ist, löschen Sie es
vv[0] = vv[0].strip()
vv[1] = vv[1].strip()
#Einige Werte sind in doppelte Anführungszeichen eingeschlossen. Löschen Sie sie daher.
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()
#Mit Polsterung bei der Verschlüsselung mit AES`_`Wird hinzugefügt, löschen Sie diesen Betrag
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()
#Da die Länge beim Verschlüsseln ein Vielfaches von 16 sein muss, ist sie mit Polsterung gefüllt
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
Die Einstellungen für Lambda und CloudFront sind dieselben wie für die Standardauthentifizierung, daher gibt es nichts zu beschreiben.
Die AES-Verschlüsselungsbibliothek muss jedoch mit pip
installiert werden, sodass ein wenig Unterstützung erforderlich ist.
Da "pip" auf Lambda nicht ausgeführt werden kann, muss die "pip install" als Zip-Datei auf einen lokalen PC hochgeladen werden.
Zu diesem Zeitpunkt ist zu beachten, dass das Betriebssystem von AWS Lambda Amazon Linux ist. Beachten Sie, dass selbst wenn Sie eine Zip-Datei auf Ihrem lokalen Mac erstellen, diese nicht funktioniert und sagt: "Oh, ich sollte sie einfach komprimieren."
Sie können EC2 für Amazon Linux erstellen und eine Zip-Datei erstellen, Docker reicht jedoch aus, da Sie höchstens eine Zip-Datei erstellen. Also habe ich es mit Docker erstellt.
#Ziehen Sie das Amazon Linux 2-Image und starten Sie es
$ docker run -it amazonlinux:2 bash
#Installieren Sie die erforderlichen Pakete auf dem Docker-Image
$ yum install -y gcc python3 pip3 python3-devel.x86_64
#Installieren Sie Pakete zur Verwendung auf Lambda
$ pip install pycrypto -t ./
#Erstellen Sie eine Zip-Datei
$ zip -r pycrypto.zip Crypto/
Wenn Sie die Zip-Datei hochladen, ist lambda_function.py
nicht mehr vorhanden. Erstellen Sie also erneut lambda_function.py
und schreiben Sie lambda_handler
.
Recommended Posts