summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Godwin2022-11-06 14:14:08 -0700
committerAndrew Godwin2022-11-06 14:14:08 -0700
commit52c83c67bbb7c3597d2fcc8fd3554927a252fedb (patch)
treecff1a0a103653c74b18b5abb23900c929c7483ba
parentdbe57075d386d7474bafc208b654507d9a2d769e (diff)
downloadtakahe-52c83c67bbb7c3597d2fcc8fd3554927a252fedb.tar.gz
takahe-52c83c67bbb7c3597d2fcc8fd3554927a252fedb.tar.bz2
takahe-52c83c67bbb7c3597d2fcc8fd3554927a252fedb.zip
Signing works with OpenSSL.
Will have to ask the cryptography peeps what I was doing wrong.
-rw-r--r--.pre-commit-config.yaml1
-rw-r--r--core/signatures.py22
-rw-r--r--requirements.txt1
-rw-r--r--users/models/identity.py42
-rw-r--r--users/views/identity.py4
5 files changed, 43 insertions, 27 deletions
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 9fa3f15..74f9cbe 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -35,3 +35,4 @@ repos:
rev: v0.982
hooks:
- id: mypy
+ additional_dependencies: [types-pyopenssl]
diff --git a/core/signatures.py b/core/signatures.py
index bcacb68..a5e4fed 100644
--- a/core/signatures.py
+++ b/core/signatures.py
@@ -1,5 +1,5 @@
import base64
-from typing import Any, Dict, List
+from typing import List, TypedDict
from cryptography.hazmat.primitives import hashes
from django.http import HttpRequest
@@ -38,11 +38,23 @@ class HttpSignature:
return "\n".join(f"{name.lower()}: {value}" for name, value in headers.items())
@classmethod
- def parse_signature(cls, signature) -> Dict[str, Any]:
- signature_details = {}
+ def parse_signature(cls, signature) -> "SignatureDetails":
+ bits = {}
for item in signature.split(","):
name, value = item.split("=", 1)
value = value.strip('"')
- signature_details[name.lower()] = value
- signature_details["headers"] = signature_details["headers"].split()
+ bits[name.lower()] = value
+ signature_details: SignatureDetails = {
+ "headers": bits["headers"].split(),
+ "signature": base64.b64decode(bits["signature"]),
+ "algorithm": bits["algorithm"],
+ "keyid": bits["keyid"],
+ }
return signature_details
+
+
+class SignatureDetails(TypedDict):
+ algorithm: str
+ headers: List[str]
+ signature: bytes
+ keyid: str
diff --git a/requirements.txt b/requirements.txt
index 2a08e47..09de1e6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,3 +5,4 @@ urlman~=2.0.1
django-crispy-forms~=1.14
cryptography~=38.0
httpx~=0.23
+pyOpenSSL~=22.1.0
diff --git a/users/models/identity.py b/users/models/identity.py
index b5f9897..4939535 100644
--- a/users/models/identity.py
+++ b/users/models/identity.py
@@ -7,13 +7,12 @@ from urllib.parse import urlparse
import httpx
import urlman
from asgiref.sync import sync_to_async
-from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa
-from django.conf import settings
from django.db import models
from django.utils import timezone
from django.utils.http import http_date
+from OpenSSL import crypto
from core.ld import canonicalise
from users.models.domain import Domain
@@ -96,14 +95,19 @@ class Identity(models.Model):
return None
@classmethod
- def by_actor_uri(cls, uri, create=False):
+ def by_actor_uri(cls, uri) -> Optional["Identity"]:
try:
return cls.objects.get(actor_uri=uri)
except cls.DoesNotExist:
- if create:
- return cls.objects.create(actor_uri=uri, local=False)
return None
+ @classmethod
+ def by_actor_uri_with_create(cls, uri) -> "Identity":
+ try:
+ return cls.objects.get(actor_uri=uri)
+ except cls.DoesNotExist:
+ return cls.objects.create(actor_uri=uri, local=False)
+
@property
def handle(self):
return f"{self.username}@{self.domain_id}"
@@ -219,7 +223,7 @@ class Identity(models.Model):
)
return base64.b64encode(
private_key.sign(
- cleartext.encode("utf8"),
+ cleartext.encode("ascii"),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH,
@@ -228,22 +232,19 @@ class Identity(models.Model):
)
).decode("ascii")
- def verify_signature(self, crypttext: str, cleartext: str) -> bool:
+ def verify_signature(self, signature: bytes, cleartext: str) -> bool:
if not self.public_key:
raise ValueError("Cannot verify - no public key")
- public_key = serialization.load_pem_public_key(self.public_key.encode("ascii"))
- print("sig??", crypttext, cleartext)
- try:
- public_key.verify(
- crypttext.encode("utf8"),
- cleartext.encode("utf8"),
- padding.PSS(
- mgf=padding.MGF1(hashes.SHA256()),
- salt_length=padding.PSS.MAX_LENGTH,
- ),
- hashes.SHA256(),
+ x509 = crypto.X509()
+ x509.set_pubkey(
+ crypto.load_publickey(
+ crypto.FILETYPE_PEM,
+ self.public_key.encode("ascii"),
)
- except InvalidSignature:
+ )
+ try:
+ crypto.verify(x509, signature, cleartext.encode("ascii"), "sha256")
+ except crypto.Error:
return False
return True
@@ -264,7 +265,7 @@ class Identity(models.Model):
del headers["(request-target)"]
headers[
"Signature"
- ] = f'keyId="https://{settings.DEFAULT_DOMAIN}{self.urls.actor}",headers="{headers_string}",signature="{signature}"'
+ ] = f'keyId="{self.urls.key.full()}",headers="{headers_string}",signature="{signature}"'
async with httpx.AsyncClient() as client:
return await client.request(
method,
@@ -288,6 +289,7 @@ class Identity(models.Model):
view = "/@{self.username}@{self.domain_id}/"
view_short = "/@{self.username}/"
actor = "{view}actor/"
+ key = "{actor}#main-key"
inbox = "{actor}inbox/"
outbox = "{actor}outbox/"
activate = "{view}activate/"
diff --git a/users/views/identity.py b/users/views/identity.py
index 1beef2a..7134cf9 100644
--- a/users/views/identity.py
+++ b/users/views/identity.py
@@ -133,7 +133,7 @@ class Actor(View):
"inbox": identity.urls.inbox.full(),
"preferredUsername": identity.username,
"publicKey": {
- "id": identity.urls.actor.full() + "#main-key",
+ "id": identity.urls.key.full(),
"owner": identity.urls.actor.full(),
"publicKeyPem": identity.public_key,
},
@@ -181,7 +181,7 @@ class Inbox(View):
print(headers_string)
print(document)
# Find the Identity by the actor on the incoming item
- identity = Identity.by_actor_uri(document["actor"], create=True)
+ identity = Identity.by_actor_uri_with_create(document["actor"])
if not identity.public_key:
# See if we can fetch it right now
async_to_sync(identity.fetch_actor)()