Signing works with OpenSSL.
Will have to ask the cryptography peeps what I was doing wrong.
This commit is contained in:
		
							parent
							
								
									dbe57075d3
								
							
						
					
					
						commit
						52c83c67bb
					
				@ -35,3 +35,4 @@ repos:
 | 
			
		||||
    rev: v0.982
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: mypy
 | 
			
		||||
        additional_dependencies: [types-pyopenssl]
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -5,3 +5,4 @@ urlman~=2.0.1
 | 
			
		||||
django-crispy-forms~=1.14
 | 
			
		||||
cryptography~=38.0
 | 
			
		||||
httpx~=0.23
 | 
			
		||||
pyOpenSSL~=22.1.0
 | 
			
		||||
 | 
			
		||||
@ -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/"
 | 
			
		||||
 | 
			
		||||
@ -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)()
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user