diff --git a/.gitignore b/.gitignore index 71fe82ac49a540cf810a901a45a1c318025f7fa5..903ce7642bdef147ae4ca6b1b4a54ad8ffbdfbca 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ CACHE .env app/keys_home/static/react/ node_modules/ +app/venv/ diff --git a/app/owlca/forms.py b/app/owlca/forms.py index 0b5759ca2c3fdf19aac3b441e3ad80658eb51305..05b00f41c907051810ef46c07ff1825ad080d828 100644 --- a/app/owlca/forms.py +++ b/app/owlca/forms.py @@ -55,9 +55,36 @@ class CaCreateForm(forms.Form): password = cleaned_data.get('password') repeat = cleaned_data.get('repeat') - if password and repeat: - if password != repeat: - raise forms.ValidationError("The two password fields must match") + if password != repeat: + raise forms.ValidationError("The two password fields must match") + + return cleaned_data + +class CaEditForm(forms.Form): + old_password = forms.CharField( + max_length=128, + widget=forms.PasswordInput(), + required=True, + ) + new_password = forms.CharField( + max_length=128, + widget=forms.PasswordInput(), + required=True, + ) + repeat = forms.CharField( + max_length=128, + widget=forms.PasswordInput(), + required=True, + ) + def clean(self): + """Custom validation to match passwords""" + cleaned_data = super().clean() + + password = cleaned_data.get('new_password') + repeat = cleaned_data.get('repeat') + + if password != repeat: + raise forms.ValidationError("The two password fields must match") return cleaned_data diff --git a/app/owlca/models.py b/app/owlca/models.py index 1d9c956a48ea4db75eb96f4d6f602562a7a4f8dc..c34312a5f7eb9ca8f3622c9565a175415c257e22 100644 --- a/app/owlca/models.py +++ b/app/owlca/models.py @@ -66,6 +66,26 @@ class CertificationAuthority(models.Model): "Can request a certificate from the CA", ) ] + + + def change_password(self, old, new): + """ + Changes the passphrase of the CA. + + Raises a ValueError if decryption fails, otherwise returns nothing. + """ + # Decrypt key with old key + key = serialization.load_pem_private_key( + self._key_pem.encode(), + old, + ) + + # Encrypt key with new key + self._key_pem = key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.BestAvailableEncryption(new) + ).decode() def generate_key(self, passphrase, length=4096): diff --git a/app/owlca/templates/owlca/certificationauthority_detail.html b/app/owlca/templates/owlca/certificationauthority_detail.html index 0e5db150e62d5f9aa0c4a87ccbd901ae128d295f..019be6a96b545a692ce6281676dc9eb2e769dcb5 100644 --- a/app/owlca/templates/owlca/certificationauthority_detail.html +++ b/app/owlca/templates/owlca/certificationauthority_detail.html @@ -21,11 +21,18 @@ <i class="fas fa-stamp"></i> {{ certificationauthority.title }} </div> - {% if "request_certificate" in ca_perms %} - <a class="btn btn-outline-primary" href="{% url 'csr-create' certificationauthority.pk %}"> - <i class="fas fa-certificate"></i> Request certificate - </a> - {% endif %} + <div> + {% if "request_certificate" in ca_perms %} + <a class="btn btn-outline-primary" href="{% url 'csr-create' certificationauthority.pk %}"> + <i class="fas fa-certificate"></i> Request certificate + </a> + {% endif %} + {% if "change_certificationauthority" in ca_perms %} + <a class="btn btn-outline-primary" href="{% url 'ca-edit' certificationauthority.pk %}"> + <i class="fas fa-pen"></i> Change password + </a> + {% endif %} + </div> </h2> <p class="lead">{{ certificationauthority.comment }}</p> diff --git a/app/owlca/templates/owlca/certificationauthority_edit.html b/app/owlca/templates/owlca/certificationauthority_edit.html new file mode 100644 index 0000000000000000000000000000000000000000..cb48245c5e771022359db19b7ef5e840ba776058 --- /dev/null +++ b/app/owlca/templates/owlca/certificationauthority_edit.html @@ -0,0 +1,25 @@ +{% extends 'owlca/base.html' %} +{% load crispy_forms_tags %} + +{% block content %} + +<nav class="nav-breadcrumb" aria-label="breadcrumb"> +<ol> + <li><a href="{% url 'home' %}">Home</a></li> + <li aria-current="page"> + <a href="{% url 'ca-list' %}">Certification Authorities</a> + </li> + <li aria-current="page"> + <a href="{% url 'ca-detail' certificationauthority.pk %}">{{ certificationauthority.title }}</a> + </li> +</ol> +</nav> + +<h2>Change password of Certification Authority</h2> +<form action="" method="post"> + {% csrf_token %} + {{ form|crispy }} + <button class="btn btn-primary" type="submit">Update</button> +</form> + +{% endblock %} diff --git a/app/owlca/urls.py b/app/owlca/urls.py index ece4fc454c2f74243d0df61f251ab32aa68cd008..1c1f8cc51f595c956d533687b9eea430e8bd051d 100644 --- a/app/owlca/urls.py +++ b/app/owlca/urls.py @@ -22,6 +22,7 @@ urlpatterns = [ path("ca/", views.CaListView.as_view(), name="ca-list"), path("ca/<int:pk>/", views.CaDetailView.as_view(), name="ca-detail"), path("ca/new/", views.ca_create, name="ca-create"), + path("ca/<int:pk>/edit/", views.ca_edit, name="ca-edit"), path("ca/<int:pk>/csr/", views.csr_create, name="csr-create"), path("csr/", views.CsrListView.as_view(), name="csr-list"), diff --git a/app/owlca/views.py b/app/owlca/views.py index 3532e5b97e6c7842311017f5003fa57d1c1f1d0b..4eec3dde221da10768c420bd2f0f45b285dbee9a 100644 --- a/app/owlca/views.py +++ b/app/owlca/views.py @@ -160,6 +160,33 @@ def ca_create(request): 'owlca/certificationauthority_create.html', {'form': form}) +@permission_required('owlca.change_certificationauthority') +def ca_edit(request, pk): + ca = get_object_or_404(models.CertificationAuthority, pk=pk) + if request.method == 'POST': + form = forms.CaEditForm(request.POST) + if form.is_valid(): + old_password = form.cleaned_data['old_password'].encode() + new_password = form.cleaned_data['new_password'].encode() + repeat = form.cleaned_data['repeat'].encode() + + try: + ca.change_password(old_password, new_password) + ca.save() + + return HttpResponseRedirect(reverse('ca-detail', args=[ca.pk])) + except ValueError: + form.add_error("old_password", "Decryption of CA key failed") + + + # if a GET (or any other method) we'll create a blank form + else: + form = forms.CaEditForm() + + return render(request, + 'owlca/certificationauthority_edit.html', + {'form': form, 'certificationauthority': ca}) + def inherit_perm(origin, origin_perm, target, target_perms): """Grant target_perm@target to all users who have origin_perm@origin""" authorized_users = get_users_with_perms(