Verified Commit 18c074f6 authored by Mattia Rizzolo's avatar Mattia Rizzolo
Browse files

Merge branch 'keyserver' of salsa.debian.org:lyknode/debexpo into live

MR: !185


Signed-off-by: Mattia Rizzolo's avatarMattia Rizzolo <mattia@debian.org>
parents 9a9dba52 6ec4abb5
Pipeline #305770 passed with stage
in 9 minutes and 35 seconds
...@@ -109,6 +109,31 @@ ...@@ -109,6 +109,31 @@
</li> </li>
</ul> </ul>
<p>
{% blocktrans with name=settings.SITE_NAME trimmed %}
You might also want to retrieve the maintainer GPG key to verify that all
subsequent uploads are signed using the same key. While the key should be
publicly available on servers such as <em>keys.openpgp.org</em> or
<em>keyserver.ubuntu.com</em>, {{ name }} can also act as a basic keyserver.
Note that only key retrieval is implemented. Update or search are not
available.
{% endblocktrans %}
</p>
<p>
{% blocktrans trimmed %}
Given a key id, you can retrieve a key using:
{% endblocktrans %}
</p>
<pre>
{% if request.scheme == 'https' %}
gpg --keyserver hkps://{{ request.get_host }} --recv-keys 0x123456789
{% else %}
gpg --keyserver hkp://{{ request.get_host }} --recv-keys 0x123456789
{% endif %}
</pre>
<h3>{% trans 'Established sponsor guidelines' %}</h3> <h3>{% trans 'Established sponsor guidelines' %}</h3>
<p> <p>
......
# views.py - keyring view classes
#
# This file is part of debexpo
# https://salsa.debian.org/mentors.debian.net-team/debexpo
#
# Copyright © 2021 Baptiste Beauplat <lyknode@debian.org>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
from django.views.generic import View
from django.views.generic.detail import BaseDetailView
from django.http import HttpResponseServerError, HttpResponse, \
HttpResponseBadRequest
from django.shortcuts import get_object_or_404
from debexpo.keyring.models import Key
class HKPSearchView(BaseDetailView):
model = Key
def get(self, request, *args, **kwargs):
if 'search' not in self.request.GET:
return HttpResponseBadRequest("Missing 'search' query string")
try:
return super().get(request, *args, **kwargs)
except Key.MultipleObjectsReturned:
return HttpResponseServerError('Multiple matches found', status=500)
def get_object(self, queryset=None):
key_id = self.request.GET['search'].replace('0x', '')
try:
return Key.objects.get(fingerprint__endswith=key_id)
except Key.DoesNotExist:
pass
return get_object_or_404(Key, subkey__fingerprint__endswith=key_id)
def render_to_response(self, context, **response_kwargs):
return HttpResponse(self.object.key + '\n')
class HKPView(View):
def get(self, request, *args, **kwargs):
if 'op' not in self.request.GET:
return HttpResponseBadRequest("Missing 'op' query string")
if self.request.GET['op'] == 'get':
return HKPSearchView.as_view()(request)
else:
return HttpResponseServerError('Not Implemented', status=501)
...@@ -41,6 +41,7 @@ from debexpo.base.views import index, contact, intro_reviewers, \ ...@@ -41,6 +41,7 @@ from debexpo.base.views import index, contact, intro_reviewers, \
intro_maintainers, qa, sponsor_overview, sponsor_guidelines, sponsor_rfs intro_maintainers, qa, sponsor_overview, sponsor_guidelines, sponsor_rfs
from debexpo.accounts.views import register, profile, EmailChangeConfirmView from debexpo.accounts.views import register, profile, EmailChangeConfirmView
from debexpo.accounts.forms import PasswordResetForm from debexpo.accounts.forms import PasswordResetForm
from debexpo.keyring.views import HKPView
from debexpo.packages.views import package, packages, packages_my, \ from debexpo.packages.views import package, packages, packages_my, \
PackagesFeed, sponsor_package, delete_package, delete_upload, \ PackagesFeed, sponsor_package, delete_package, delete_upload, \
PackageViewSet, PackageUploadViewSet PackageViewSet, PackageUploadViewSet
...@@ -167,6 +168,9 @@ urlpatterns = [ ...@@ -167,6 +168,9 @@ urlpatterns = [
url(r'^accounts/$', lambda request: HttpResponsePermanentRedirect( url(r'^accounts/$', lambda request: HttpResponsePermanentRedirect(
reverse('profile')), name='accounts'), reverse('profile')), name='accounts'),
# HPK interface
url(r'^pks/lookup$', HKPView.as_view(), name='hkp'),
# Repository # Repository
url(r'^debian/(?P<path>.*)$', serve, { url(r'^debian/(?P<path>.*)$', serve, {
'document_root': settings.REPOSITORY, 'document_root': settings.REPOSITORY,
......
...@@ -129,6 +129,8 @@ Xcgnuh6Rlywt6uiaFIGYnGefYPGXRAA= ...@@ -129,6 +129,8 @@ Xcgnuh6Rlywt6uiaFIGYnGefYPGXRAA=
self._add_gpg_key(user, self._GPG_KEY, self._GPG_FINGERPRINT, self._add_gpg_key(user, self._GPG_KEY, self._GPG_FINGERPRINT,
self._GPG_TYPE, self._GPG_SIZE) self._GPG_TYPE, self._GPG_SIZE)
return user
def _remove_example_user(self): def _remove_example_user(self):
"""Remove the example user. """Remove the example user.
......
# test_hkp.py - Test HKP implementation
#
# This file is part of debexpo
# https://salsa.debian.org/mentors.debian.net-team/debexpo
#
# Copyright © 2021 Baptiste Beauplat <lyknode@cilg.org>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
from django.test import LiveServerTestCase
from debexpo.tools.gnupg import GnuPG
from tests import TransactionTestController
class TestHKP(TransactionTestController, LiveServerTestCase):
def setUp(self):
self._setup_example_user(gpg=True)
def test_gpg_recv_keys(self):
gpg = GnuPG()
# On existing key
(output, status) = gpg._run(args=[
'--keyserver',
'hkp://' + self.live_server_url.split('/')[2],
'--recv-keys',
'0x' + self._GPG_FINGERPRINT[-16:]])
self.assertIn('IMPORT_OK 1', output)
self.assertEquals(0, status)
# On non-existing key
(output, status) = gpg._run(args=[
'--keyserver',
'hkp://' + self.live_server_url.split('/')[2],
'--recv-keys',
'0xCA11AB1E'])
self.assertIn('FAILURE recv-keys', output)
self.assertEquals(2, status)
# test_hkp.py - Test HKP implementation
#
# This file is part of debexpo
# https://salsa.debian.org/mentors.debian.net-team/debexpo
#
# Copyright © 2021 Baptiste Beauplat <lyknode@cilg.org>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
from django.urls import reverse
from debexpo.keyring.models import SubKey
from tests import TestController
_GPGKEY = """-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEW9b91RYJKwYBBAHaRw8BAQdAHtUIQWAsmPilu0JDMnLbpPQfT1i3z2IVMoDH
rhlYkO+0JWRlYmV4cG8gdGVzdGluZyA8ZGViZXhwb0BleGFtcGxlLm9yZz6IkAQT
FggAOBYhBOF57qTrR+YF2YZjLihiGOfHT5wRBQJb1v3VAhsDBQsJCAcCBhUKCQgL
AgQWAgMBAh4BAheAAAoJEChiGOfHT5wRdQIBAJ8rciR0e1PaA+LhoTWHaPSgCwvc
lNFyRk71s75+hRkhAPwPnl6QqGsOa0DyJB5saVcqPCqYFbF1usUWIQnPPRsVC7g4
BFvW/dUSCisGAQQBl1UBBQEBB0DzrYDCp+OaNFinqKkDWcqftqq/BAFS9lq4de5g
RNytNAMBCAeIeAQYFggAIBYhBOF57qTrR+YF2YZjLihiGOfHT5wRBQJb1v3VAhsM
AAoJEChiGOfHT5wRNK8A/115pc8+OwKDy1fGXGX3l0uq1wdfiJreG/9YZddx/JTI
AQD4ZLpyUg+z6kJ+8YAmHFiOD9Ixv3QVvrfpBwnBVtJZBg==
=N+9W
-----END PGP PUBLIC KEY BLOCK-----"""
class TestHKP(TestController):
def setUp(self):
self._setup_example_user(gpg=True)
another = self._setup_example_user(email='another@example.org')
self._add_gpg_key(another, _GPGKEY,
'FINGERPRINT', '22', 256)
def test_op(self):
# Missing op parameter: 400
response = self.client.get(reverse('hkp'))
self.assertEquals(response.status_code, 400)
self.assertIn("Missing 'op'", str(response.content))
# Unknown op: 501
response = self.client.get(reverse('hkp'),
{'op': 'not-implemented'})
self.assertEquals(response.status_code, 501)
self.assertIn("Not Implemented", str(response.content))
def test_get(self):
# Missing search parameter: 400
response = self.client.get(reverse('hkp'),
{'op': 'get'})
self.assertEquals(response.status_code, 400)
self.assertIn("Missing 'search'", str(response.content))
# No match: 404
response = self.client.get(reverse('hkp'),
{'op': 'get',
'search': 'not-found'})
self.assertEquals(response.status_code, 404)
self.assertIn('Not Found', str(response.content))
# Multiple matches: 500
response = self.client.get(reverse('hkp'),
{'op': 'get',
'search': ''})
self.assertEquals(response.status_code, 500)
self.assertIn("Multiple matches found", str(response.content))
# Matches long id: 200
response = self.client.get(reverse('hkp'), {
'op': 'get',
'search': '0x' + self._GPG_FINGERPRINT[-16:]
})
self.assertEquals(response.status_code, 200)
self.assertIn(self._GPG_KEY, response.content.decode())
# Matches full fingerprint of a subkey: 200
fingerprint = SubKey.objects.filter(
key__fingerprint=self._GPG_FINGERPRINT).first().fingerprint
response = self.client.get(reverse('hkp'), {
'op': 'get',
'search': fingerprint
})
self.assertIn(self._GPG_KEY, response.content.decode())
self.assertEquals(response.status_code, 200)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment