From b3a90a8ad6cd8044d44ef3a3b43a0921233df9e8 Mon Sep 17 00:00:00 2001 From: decentral1se <lukewm@riseup.net> Date: Mon, 15 Mar 2021 18:27:23 +0000 Subject: [PATCH] Add djangoldp_crypto app See https://git.startinblox.com/djangoldp-packages/djangoldp/issues/236. --- .gitlab-ci.yml | 11 ++++++ djangoldp_crypto/README.md | 26 +++++++++++++ djangoldp_crypto/__init__.py | 0 djangoldp_crypto/admin.py | 13 +++++++ djangoldp_crypto/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/creatersakey.py | 19 ++++++++++ djangoldp_crypto/migrations/0001_initial.py | 25 +++++++++++++ djangoldp_crypto/migrations/__init__.py | 0 djangoldp_crypto/models.py | 37 +++++++++++++++++++ djangoldp_crypto/tests/runner.py | 23 ++++++++++++ djangoldp_crypto/tests/settings_default.py | 12 ++++++ djangoldp_crypto/tests/tests_rsakey.py | 12 ++++++ setup.cfg | 2 + 14 files changed, 180 insertions(+) create mode 100644 djangoldp_crypto/README.md create mode 100644 djangoldp_crypto/__init__.py create mode 100644 djangoldp_crypto/admin.py create mode 100644 djangoldp_crypto/management/__init__.py create mode 100644 djangoldp_crypto/management/commands/__init__.py create mode 100644 djangoldp_crypto/management/commands/creatersakey.py create mode 100644 djangoldp_crypto/migrations/0001_initial.py create mode 100644 djangoldp_crypto/migrations/__init__.py create mode 100644 djangoldp_crypto/models.py create mode 100644 djangoldp_crypto/tests/runner.py create mode 100644 djangoldp_crypto/tests/settings_default.py create mode 100644 djangoldp_crypto/tests/tests_rsakey.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8814aaca..bd3470f0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,6 +16,17 @@ test: tags: - test +crypto-test: + stage: test + script: + - pip install .[crypto] + - python -m unittest djangoldp_crypto.tests.runner + except: + - master + - tags + tags: + - test + publish: stage: release before_script: diff --git a/djangoldp_crypto/README.md b/djangoldp_crypto/README.md new file mode 100644 index 00000000..d493c3d2 --- /dev/null +++ b/djangoldp_crypto/README.md @@ -0,0 +1,26 @@ +# djangoldp-crypto + +Packages like [djangoldp](https://git.startinblox.com/djangoldp-packages/djangoldp) and [django-webidoidc-provider](https://git.startinblox.com/djangoldp-packages/django-webidoidc-provider) have some models and utilities which make use of cryptography. In general, we want to re-use that code in a supporting package to avoid duplication of effort. However, until it is more clear what ca be re-used, we are using this separate django app in this package. See [this ticket](https://git.startinblox.com/djangoldp-packages/djangoldp/issues/236) for more. + +## Install + +```bash +$ python -m pip install 'djangoldp[crypto]' +``` + +Tnen add the app to your `settings.yml` like so: + +```yaml +INSTALLED_APPS: + - djangoldp_crypto +``` + +## Management commands + +- `creatersakey`: Randomly generate a new RSA key for the DjangoLDP server + +## Test + +```bash +$ python -m unittest djangoldp_crypto.tests.runner +``` diff --git a/djangoldp_crypto/__init__.py b/djangoldp_crypto/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/djangoldp_crypto/admin.py b/djangoldp_crypto/admin.py new file mode 100644 index 00000000..9bd35f63 --- /dev/null +++ b/djangoldp_crypto/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin + +from djangoldp_crypto.models import RSAKey + + +@admin.register(RSAKey) +class RSAKeyAdmin(admin.ModelAdmin): + + readonly_fields = ['kid', 'pub_key'] + + def save_model(self, request, obj, form, change): + obj.priv_key.replace('\r', '') + super().save_model(request, obj, form, change) diff --git a/djangoldp_crypto/management/__init__.py b/djangoldp_crypto/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/djangoldp_crypto/management/commands/__init__.py b/djangoldp_crypto/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/djangoldp_crypto/management/commands/creatersakey.py b/djangoldp_crypto/management/commands/creatersakey.py new file mode 100644 index 00000000..7f56c563 --- /dev/null +++ b/djangoldp_crypto/management/commands/creatersakey.py @@ -0,0 +1,19 @@ +from Cryptodome.PublicKey import RSA +from django.core.management.base import BaseCommand +from djangoldp_crypto.models import RSAKey + + +class Command(BaseCommand): + help = 'Randomly generate a new RSA key for the DjangoLDP server' + + def handle(self, *args, **options): + try: + key = RSA.generate(2048) + rsakey = RSAKey(priv_key=key.exportKey('PEM').decode('utf8')) + rsakey.save() + self.stdout.write('RSA key successfully created') + self.stdout.write(u'Private key: \n{0}'.format(rsakey.priv_key)) + self.stdout.write(u'Public key: \n{0}'.format(rsakey.pub_key)) + self.stdout.write(u'Key ID: \n{0}'.format(rsakey.kid)) + except Exception as e: + self.stdout.write('Something goes wrong: {0}'.format(e)) diff --git a/djangoldp_crypto/migrations/0001_initial.py b/djangoldp_crypto/migrations/0001_initial.py new file mode 100644 index 00000000..e6d6add6 --- /dev/null +++ b/djangoldp_crypto/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.19 on 2021-03-15 10:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='RSAKey', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('priv_key', models.TextField(help_text='Paste your private RSA Key here.', unique=True, verbose_name='Key')), + ], + options={ + 'verbose_name': 'RSA Key', + 'verbose_name_plural': 'RSA Keys', + }, + ), + ] diff --git a/djangoldp_crypto/migrations/__init__.py b/djangoldp_crypto/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/djangoldp_crypto/models.py b/djangoldp_crypto/models.py new file mode 100644 index 00000000..07c64013 --- /dev/null +++ b/djangoldp_crypto/models.py @@ -0,0 +1,37 @@ +from hashlib import md5 + +from Cryptodome.PublicKey import RSA +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class RSAKey(models.Model): + + priv_key = models.TextField( + verbose_name=_(u'Key'), unique=True, + help_text=_(u'Paste your private RSA Key here.')) + + class Meta: + verbose_name = _(u'RSA Key') + verbose_name_plural = _(u'RSA Keys') + + def __str__(self): + return u'{0}'.format(self.kid) + + def __unicode__(self): + return self.__str__() + + @property + def kid(self): + if not self.priv_key: + return '' + + return u'{0}'.format(md5(self.priv_key.encode('utf-8')).hexdigest()) + + @property + def pub_key(self): + if not self.priv_key: + return '' + + _pub_key = RSA.importKey(self.priv_key).publickey() + return _pub_key.export_key().decode('utf-8') diff --git a/djangoldp_crypto/tests/runner.py b/djangoldp_crypto/tests/runner.py new file mode 100644 index 00000000..65ba2d2e --- /dev/null +++ b/djangoldp_crypto/tests/runner.py @@ -0,0 +1,23 @@ +import sys + +import django +import yaml +from django.conf import settings as django_settings +from djangoldp.conf.ldpsettings import LDPSettings +from djangoldp_crypto.tests.settings_default import yaml_config + +# load test config +config = yaml.safe_load(yaml_config) +ldpsettings = LDPSettings(config) +django_settings.configure(ldpsettings) + +django.setup() +from django.test.runner import DiscoverRunner + +test_runner = DiscoverRunner(verbosity=1) + +failures = test_runner.run_tests([ + 'djangoldp_crypto.tests.tests_rsakey', +]) +if failures: + sys.exit(failures) diff --git a/djangoldp_crypto/tests/settings_default.py b/djangoldp_crypto/tests/settings_default.py new file mode 100644 index 00000000..da76f6c5 --- /dev/null +++ b/djangoldp_crypto/tests/settings_default.py @@ -0,0 +1,12 @@ +"""This module contains YAML configurations for djangoldp_crypto testing.""" + +yaml_config = """ +dependencies: + +ldppackages: + - djangoldp_crypto.tests + +server: + INSTALLED_APPS: + - djangoldp_crypto +""" diff --git a/djangoldp_crypto/tests/tests_rsakey.py b/djangoldp_crypto/tests/tests_rsakey.py new file mode 100644 index 00000000..d41e1fa7 --- /dev/null +++ b/djangoldp_crypto/tests/tests_rsakey.py @@ -0,0 +1,12 @@ +from Cryptodome.PublicKey import RSA +from django.db import IntegrityError +from django.test import TestCase +from djangoldp_crypto.models import RSAKey + + +class TestRSAKey(TestCase): + def test_rsakey_unique(self): + priv_key = RSA.generate(2048) + RSAKey.objects.create(priv_key=priv_key) + with self.assertRaises(IntegrityError): + RSAKey.objects.create(priv_key=priv_key) diff --git a/setup.cfg b/setup.cfg index 8858c1ae..3dd375f7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,6 +38,8 @@ console_scripts = dev = validators factory_boy >= 2.11.0 +crypto = + pycryptodomex~=3.10 [semantic_release] version_source = tag -- GitLab