diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8814aaca96d66ae9fbf04eed8b440f44843a3509..bd3470f0b2971751657a0ebf8cd99c111574f325 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 0000000000000000000000000000000000000000..d493c3d2af60135eb762138144a5f9c36a19746e --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/djangoldp_crypto/admin.py b/djangoldp_crypto/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..9bd35f635d3cc44248fe1b30b7c86585b482acb6 --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/djangoldp_crypto/management/commands/__init__.py b/djangoldp_crypto/management/commands/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/djangoldp_crypto/management/commands/creatersakey.py b/djangoldp_crypto/management/commands/creatersakey.py new file mode 100644 index 0000000000000000000000000000000000000000..7f56c563c43d14e6ea2fa2bb6f612f2ad929d306 --- /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 0000000000000000000000000000000000000000..e6d6add67090f9e51a23f9af974d756969ba65fa --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/djangoldp_crypto/models.py b/djangoldp_crypto/models.py new file mode 100644 index 0000000000000000000000000000000000000000..07c64013593577373c11ca48644a328ef9069d39 --- /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 0000000000000000000000000000000000000000..65ba2d2e890f1b76113df7d0ddfc44806f3fc619 --- /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 0000000000000000000000000000000000000000..da76f6c5124b4fa4c8d1519d88a1fc3ef5f61820 --- /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 0000000000000000000000000000000000000000..d41e1fa7d352a46bd17fa85c889ba560bb751fd7 --- /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 8858c1aeb26a6d23e1878fa9c6e01b35e28a901e..3dd375f7bcf7ee82ba5088a2158008f509649c99 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