From 4b65258f1cf05c6c3bdbd1d4e781446f8e169120 Mon Sep 17 00:00:00 2001 From: Jeff Tindell Date: Thu, 21 Sep 2017 15:25:07 -0400 Subject: [PATCH 1/4] uses pycryptodome --- netbox/secrets/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/secrets/models.py b/netbox/secrets/models.py index e8eb2ff45..b8f7e8e3e 100644 --- a/netbox/secrets/models.py +++ b/netbox/secrets/models.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import os -from Crypto.Cipher import AES, PKCS1_OAEP, XOR +from Crypto.Cipher import AES, PKCS1_OAEP from Crypto.PublicKey import RSA from django.conf import settings From c42c79b14764893afde1fcdfe155443131cfbfdd Mon Sep 17 00:00:00 2001 From: Jeff Tindell Date: Thu, 21 Sep 2017 15:26:31 -0400 Subject: [PATCH 2/4] Requires cryptodome --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8cd72419f..966020478 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,6 @@ paramiko>=2.0.0 Pillow>=4.0.0 psycopg2>=2.6.1 py-gfm>=0.1.3 -pycrypto>=2.6.1 +pycryptodome>=3.4.5 sqlparse>=0.2 xmltodict>=0.10.2 From b39e158a9c6632c6200d47d3a4a3d85884dde786 Mon Sep 17 00:00:00 2001 From: Jeff Tindell Date: Fri, 22 Sep 2017 10:27:44 -0400 Subject: [PATCH 3/4] Updated to use AES --- netbox/secrets/models.py | 59 ++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/netbox/secrets/models.py b/netbox/secrets/models.py index b8f7e8e3e..73cd98acd 100644 --- a/netbox/secrets/models.py +++ b/netbox/secrets/models.py @@ -12,8 +12,10 @@ from django.db import models from django.urls import reverse from django.utils.encoding import force_bytes, python_2_unicode_compatible -from dcim.models import Device -from utilities.models import CreatedUpdatedModel +# from dcim.models import Device +# from utilities.models import CreatedUpdatedModel +from netbox.dcim.models import Device +from netbox.utilities.models import CreatedUpdatedModel from .exceptions import InvalidKey from .hashers import SecretValidationHasher @@ -45,16 +47,42 @@ def decrypt_master_key(master_key_cipher, private_key): return cipher.decrypt(master_key_cipher) -def xor_keys(key_a, key_b): - """ - Return the binary XOR of two given keys. - """ - xor = XOR.new(key_a) - return xor.encrypt(key_b) +# def xor_keys(key_a, key_b): +# """ +# Return the binary XOR of two given keys. +# """ +# # encrypts "b" with "a" +## key_b must be a bytstring +# cipher = AES.new(key_a, AES.MODE_CBC) +# # xor = XOR.new(key_a) +# # return xor.encrypt(key_b) +# return cipher + + +def aes_encrypt_key(key, plaintext): + # encrypt b with a + cipher = AES.new(key, AES.MODE_EAX) + nonce = cipher.nonce + ciphertext, tag = cipher.encrypt_and_digest(plaintext) + # xor = XOR.new(key_a) + # return xor.encrypt(key_b) + return ciphertext, tag, nonce + + +def aes_decrypt_key(key, cyphertext, nonce, tag): + # decrypt b with a + cipher = AES.new(key, AES.MODE_EAX, nonce=nonce) + plaintext = cipher.dectypt(cyphertext) + try: + cipher.verify(tag) + except ValueError: + raise ValidationError({ + 'aes decrypt': "Key incorrect or message corrupted" + }) + return plaintext class UserKeyQuerySet(models.QuerySet): - def active(self): return self.filter(master_key_cipher__isnull=False) @@ -153,6 +181,7 @@ class UserKey(CreatedUpdatedModel): Returns True if the UserKey has been filled with a public RSA key. """ return bool(self.public_key) + is_filled.boolean = True def is_active(self): @@ -160,6 +189,7 @@ class UserKey(CreatedUpdatedModel): Returns True if the UserKey has been populated with an encrypted copy of the master key. """ return self.master_key_cipher is not None + is_active.boolean = True def get_master_key(self, private_key): @@ -194,6 +224,8 @@ class SessionKey(models.Model): created = models.DateTimeField(auto_now_add=True) key = None + nonce = None + tag = None class Meta: ordering = ['userkey__user__username'] @@ -214,7 +246,8 @@ class SessionKey(models.Model): self.hash = make_password(self.key) # Encrypt master key using the session key - self.cipher = xor_keys(self.key, master_key) + # self.cipher = xor_keys(self.key, master_key) + self.cipher, self.tag, self.nonce = aes_encrypt_key(self.key, master_key) super(SessionKey, self).save(*args, **kwargs) @@ -225,14 +258,16 @@ class SessionKey(models.Model): raise InvalidKey("Invalid session key") # Decrypt master key using provided session key - master_key = xor_keys(session_key, bytes(self.cipher)) + # master_key = xor_keys(session_key, bytes(self.cipher)) + master_key = aes_decrypt_key(session_key, bytes(self.cipher), self.nonce, self.tag) return master_key def get_session_key(self, master_key): # Recover session key using the master key - session_key = xor_keys(master_key, bytes(self.cipher)) + # session_key = xor_keys(master_key, bytes(self.cipher)) + session_key = aes_decrypt_key(master_key, bytes(self.cipher), self.nonce, self.tag) # Validate the recovered session key if not check_password(session_key, self.hash): From 089446e3059e63ae01fe2b018126f8bc6a950ddf Mon Sep 17 00:00:00 2001 From: Jeff Tindell Date: Fri, 22 Sep 2017 10:36:02 -0400 Subject: [PATCH 4/4] fixes imports --- netbox/secrets/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/netbox/secrets/models.py b/netbox/secrets/models.py index 73cd98acd..24f69200b 100644 --- a/netbox/secrets/models.py +++ b/netbox/secrets/models.py @@ -12,10 +12,10 @@ from django.db import models from django.urls import reverse from django.utils.encoding import force_bytes, python_2_unicode_compatible -# from dcim.models import Device -# from utilities.models import CreatedUpdatedModel -from netbox.dcim.models import Device -from netbox.utilities.models import CreatedUpdatedModel +from dcim.models import Device +from utilities.models import CreatedUpdatedModel +#from netbox.dcim.models import Device +#from netbox.utilities.models import CreatedUpdatedModel from .exceptions import InvalidKey from .hashers import SecretValidationHasher