summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorMagnus Hagander2025-04-01 11:41:08 +0000
committerMagnus Hagander2025-06-11 18:26:21 +0000
commitde76f82f62c0818f041c28a7a5f4ccdcb2e3a1ab (patch)
tree1ad83270e4be3e1285833f9d4d494ffcc089211a /tools
parent7a42e2a5f595466d0632cd2f8cd1893b88866619 (diff)
Implement authenticated encryption in community auth
This creates a community auth version 3 (previous one being 2, and 1 is long gone) trhat uses AES_SIV as the encryption method instead of regular AES_CBC, and validates the digests on all accounts. As this gets deployed on servers incrementall, the version has to be specified in the database record for the site. We could have the site indicate this itself, but doing it this way seems safer as it will then just break for any app that accidentally reverts the plugin. Reviewed by Jacob Champion
Diffstat (limited to 'tools')
-rwxr-xr-xtools/communityauth/generate_cryptkey.py4
-rw-r--r--tools/communityauth/sample/django/auth.py75
-rwxr-xr-xtools/communityauth/test_auth.py21
3 files changed, 65 insertions, 35 deletions
diff --git a/tools/communityauth/generate_cryptkey.py b/tools/communityauth/generate_cryptkey.py
index c0ca505c..ee70d7f0 100755
--- a/tools/communityauth/generate_cryptkey.py
+++ b/tools/communityauth/generate_cryptkey.py
@@ -9,11 +9,11 @@ from Cryptodome import Random
import base64
if __name__ == "__main__":
- print("The next row contains a 32-byte (256-bit) symmetric crypto key.")
+ print("The next row contains a 64-byte (512-bit) symmetric crypto key.")
print("This key should be used to integrate a community auth site.")
print("Note that each site should have it's own key!!")
print("")
r = Random.new()
- key = r.read(32)
+ key = r.read(64)
print(base64.b64encode(key).decode('ascii'))
diff --git a/tools/communityauth/sample/django/auth.py b/tools/communityauth/sample/django/auth.py
index 9343fc0f..dc5c1fb6 100644
--- a/tools/communityauth/sample/django/auth.py
+++ b/tools/communityauth/sample/django/auth.py
@@ -41,7 +41,7 @@ import hmac
from urllib.parse import urlencode, parse_qs
import requests
from Cryptodome.Cipher import AES
-from Cryptodome.Hash import SHA
+from Cryptodome.Hash import SHA256
from Cryptodome import Random
import time
@@ -75,15 +75,19 @@ def login(request):
s = "t=%s&%s" % (int(time.time()), urlencode({'r': request.GET['next']}))
# Now encrypt it
r = Random.new()
- iv = r.read(16)
- encryptor = AES.new(SHA.new(settings.SECRET_KEY.encode('ascii')).digest()[:16], AES.MODE_CBC, iv)
- cipher = encryptor.encrypt(s.encode('ascii') + b' ' * (16 - (len(s) % 16))) # pad to 16 bytes
-
- return HttpResponseRedirect("%s?d=%s$%s" % (
- settings.PGAUTH_REDIRECT,
- base64.b64encode(iv, b"-_").decode('utf8'),
- base64.b64encode(cipher, b"-_").decode('utf8'),
- ))
+ nonce = r.read(16)
+ encryptor = AES.new(
+ SHA256.new(settings.SECRET_KEY.encode('ascii')).digest()[:32], AES.MODE_SIV, nonce=nonce
+ )
+ cipher, tag = encryptor.encrypt_and_digest(s.encode('ascii'))
+
+ return HttpResponseRedirect("%s?%s" % (settings.PGAUTH_REDIRECT, urlencode({
+ 'd': '$'.join((
+ base64.b64encode(nonce, b"-_").decode('utf8'),
+ base64.b64encode(cipher, b"-_").decode('utf8'),
+ base64.b64encode(tag, b"-_").decode('utf8'),
+ )),
+ })))
else:
return HttpResponseRedirect(settings.PGAUTH_REDIRECT)
@@ -103,17 +107,24 @@ def auth_receive(request):
# This was a logout request
return HttpResponseRedirect('/')
- if 'i' not in request.GET:
- return HttpResponse("Missing IV in url!", status=400)
+ if 'n' not in request.GET:
+ return HttpResponse("Missing nonce in url!", status=400)
if 'd' not in request.GET:
return HttpResponse("Missing data in url!", status=400)
+ if 't' not in request.GET:
+ return HttpResponse("Missing tag in url!", status=400)
# Set up an AES object and decrypt the data we received
try:
- decryptor = AES.new(base64.b64decode(settings.PGAUTH_KEY),
- AES.MODE_CBC,
- base64.b64decode(str(request.GET['i']), "-_"))
- s = decryptor.decrypt(base64.b64decode(str(request.GET['d']), "-_")).rstrip(b' ').decode('utf8')
+ decryptor = AES.new(
+ base64.b64decode(settings.PGAUTH_KEY),
+ AES.MODE_SIV,
+ nonce=base64.b64decode(str(request.GET['n']), "-_"),
+ )
+ s = decryptor.decrypt_and_verify(
+ base64.b64decode(str(request.GET['d']), "-_"),
+ base64.b64decode(str(request.GET['t']), "-_"),
+ ).rstrip(b' ').decode('utf8')
except UnicodeDecodeError:
return HttpResponse("Badly encoded data found", 400)
except Exception:
@@ -200,11 +211,16 @@ We apologize for the inconvenience.
# Finally, check of we have a data package that tells us where to
# redirect the user.
if 'd' in data:
- (ivs, datas) = data['d'][0].split('$')
- decryptor = AES.new(SHA.new(settings.SECRET_KEY.encode('ascii')).digest()[:16],
- AES.MODE_CBC,
- base64.b64decode(ivs, b"-_"))
- s = decryptor.decrypt(base64.b64decode(datas, "-_")).rstrip(b' ').decode('utf8')
+ (nonces, datas, tags) = data['d'][0].split('$')
+ decryptor = AES.new(
+ SHA256.new(settings.SECRET_KEY.encode('ascii')).digest()[:32],
+ AES.MODE_SIV,
+ nonce=base64.b64decode(nonces, b"-_"),
+ )
+ s = decryptor.decrypt_and_verify(
+ base64.b64decode(datas, "-_"),
+ base64.b64decode(tags, "-_"),
+ ).rstrip(b' ').decode('utf8')
try:
rdata = parse_qs(s, strict_parsing=True)
except ValueError:
@@ -304,17 +320,24 @@ def user_search(searchterm=None, userid=None):
r = requests.get(
'{0}search/'.format(settings.PGAUTH_REDIRECT),
params=q,
+ timeout=10,
)
if r.status_code != 200:
return []
- (ivs, datas) = r.text.encode('utf8').split(b'&')
+ (nonces, datas, tags) = r.text.encode('utf8').split(b'&')
# Decryption time
- decryptor = AES.new(base64.b64decode(settings.PGAUTH_KEY),
- AES.MODE_CBC,
- base64.b64decode(ivs, "-_"))
- s = decryptor.decrypt(base64.b64decode(datas, "-_")).rstrip(b' ').decode('utf8')
+ decryptor = AES.new(
+ base64.b64decode(settings.PGAUTH_KEY),
+ AES.MODE_SIV,
+ nonce=base64.b64decode(nonces, "-_")
+ )
+ s = decryptor.decrypt_and_verify(
+ base64.b64decode(datas, "-_"),
+ base64.b64decode(tags, "-_"),
+ ).rstrip(b' ').decode('utf8')
+
j = json.loads(s)
return j
diff --git a/tools/communityauth/test_auth.py b/tools/communityauth/test_auth.py
index ea6d1fa1..db16f288 100755
--- a/tools/communityauth/test_auth.py
+++ b/tools/communityauth/test_auth.py
@@ -54,12 +54,19 @@ if __name__ == "__main__":
s = "t=%s&%s" % (int(time.time() + 300), urllib.parse.urlencode(info))
r = Random.new()
- iv = r.read(16)
- encryptor = AES.new(base64.b64decode(options.key), AES.MODE_CBC, iv)
- cipher = encryptor.encrypt(s.encode('ascii') + b' ' * (16 - (len(s) % 16)))
+ nonce = r.read(16)
+ encryptor = AES.new(
+ base64.b64decode(options.key),
+ AES.MODE_SIV,
+ nonce=nonce,
+ )
+ cipher, tag = encryptor.encrypt_and_digest(s.encode('ascii'))
+
+ redirparams = {
+ 'd': base64.b64encode(cipher, b"-_").decode('ascii'),
+ 'n': base64.b64encode(nonce, b"-_").decode('ascii'),
+ 't': base64.b64encode(tag, b"-_").decode('ascii'),
+ }
print("Paste the following after the receiving url:")
- print("?i=%s&d=%s" % (
- base64.b64encode(iv, b"-_").decode('ascii'),
- base64.b64encode(cipher, b"-_").decode('ascii'),
- ))
+ print("?" + urllib.parse.urlencode(redirparams))