mirror of
https://github.com/jointakahe/takahe.git
synced 2024-11-25 00:30:59 +00:00
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
5 changed files with 43 additions and 27 deletions
|
@ -35,3 +35,4 @@ repos:
|
||||||
rev: v0.982
|
rev: v0.982
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
|
additional_dependencies: [types-pyopenssl]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import base64
|
import base64
|
||||||
from typing import Any, Dict, List
|
from typing import List, TypedDict
|
||||||
|
|
||||||
from cryptography.hazmat.primitives import hashes
|
from cryptography.hazmat.primitives import hashes
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
@ -38,11 +38,23 @@ class HttpSignature:
|
||||||
return "\n".join(f"{name.lower()}: {value}" for name, value in headers.items())
|
return "\n".join(f"{name.lower()}: {value}" for name, value in headers.items())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_signature(cls, signature) -> Dict[str, Any]:
|
def parse_signature(cls, signature) -> "SignatureDetails":
|
||||||
signature_details = {}
|
bits = {}
|
||||||
for item in signature.split(","):
|
for item in signature.split(","):
|
||||||
name, value = item.split("=", 1)
|
name, value = item.split("=", 1)
|
||||||
value = value.strip('"')
|
value = value.strip('"')
|
||||||
signature_details[name.lower()] = value
|
bits[name.lower()] = value
|
||||||
signature_details["headers"] = signature_details["headers"].split()
|
signature_details: SignatureDetails = {
|
||||||
|
"headers": bits["headers"].split(),
|
||||||
|
"signature": base64.b64decode(bits["signature"]),
|
||||||
|
"algorithm": bits["algorithm"],
|
||||||
|
"keyid": bits["keyid"],
|
||||||
|
}
|
||||||
return signature_details
|
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
|
django-crispy-forms~=1.14
|
||||||
cryptography~=38.0
|
cryptography~=38.0
|
||||||
httpx~=0.23
|
httpx~=0.23
|
||||||
|
pyOpenSSL~=22.1.0
|
||||||
|
|
|
@ -7,13 +7,12 @@ from urllib.parse import urlparse
|
||||||
import httpx
|
import httpx
|
||||||
import urlman
|
import urlman
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
from cryptography.exceptions import InvalidSignature
|
|
||||||
from cryptography.hazmat.primitives import hashes, serialization
|
from cryptography.hazmat.primitives import hashes, serialization
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding, rsa
|
from cryptography.hazmat.primitives.asymmetric import padding, rsa
|
||||||
from django.conf import settings
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.http import http_date
|
from django.utils.http import http_date
|
||||||
|
from OpenSSL import crypto
|
||||||
|
|
||||||
from core.ld import canonicalise
|
from core.ld import canonicalise
|
||||||
from users.models.domain import Domain
|
from users.models.domain import Domain
|
||||||
|
@ -96,14 +95,19 @@ class Identity(models.Model):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def by_actor_uri(cls, uri, create=False):
|
def by_actor_uri(cls, uri) -> Optional["Identity"]:
|
||||||
try:
|
try:
|
||||||
return cls.objects.get(actor_uri=uri)
|
return cls.objects.get(actor_uri=uri)
|
||||||
except cls.DoesNotExist:
|
except cls.DoesNotExist:
|
||||||
if create:
|
|
||||||
return cls.objects.create(actor_uri=uri, local=False)
|
|
||||||
return None
|
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
|
@property
|
||||||
def handle(self):
|
def handle(self):
|
||||||
return f"{self.username}@{self.domain_id}"
|
return f"{self.username}@{self.domain_id}"
|
||||||
|
@ -219,7 +223,7 @@ class Identity(models.Model):
|
||||||
)
|
)
|
||||||
return base64.b64encode(
|
return base64.b64encode(
|
||||||
private_key.sign(
|
private_key.sign(
|
||||||
cleartext.encode("utf8"),
|
cleartext.encode("ascii"),
|
||||||
padding.PSS(
|
padding.PSS(
|
||||||
mgf=padding.MGF1(hashes.SHA256()),
|
mgf=padding.MGF1(hashes.SHA256()),
|
||||||
salt_length=padding.PSS.MAX_LENGTH,
|
salt_length=padding.PSS.MAX_LENGTH,
|
||||||
|
@ -228,22 +232,19 @@ class Identity(models.Model):
|
||||||
)
|
)
|
||||||
).decode("ascii")
|
).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:
|
if not self.public_key:
|
||||||
raise ValueError("Cannot verify - no public key")
|
raise ValueError("Cannot verify - no public key")
|
||||||
public_key = serialization.load_pem_public_key(self.public_key.encode("ascii"))
|
x509 = crypto.X509()
|
||||||
print("sig??", crypttext, cleartext)
|
x509.set_pubkey(
|
||||||
try:
|
crypto.load_publickey(
|
||||||
public_key.verify(
|
crypto.FILETYPE_PEM,
|
||||||
crypttext.encode("utf8"),
|
self.public_key.encode("ascii"),
|
||||||
cleartext.encode("utf8"),
|
|
||||||
padding.PSS(
|
|
||||||
mgf=padding.MGF1(hashes.SHA256()),
|
|
||||||
salt_length=padding.PSS.MAX_LENGTH,
|
|
||||||
),
|
|
||||||
hashes.SHA256(),
|
|
||||||
)
|
)
|
||||||
except InvalidSignature:
|
)
|
||||||
|
try:
|
||||||
|
crypto.verify(x509, signature, cleartext.encode("ascii"), "sha256")
|
||||||
|
except crypto.Error:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -264,7 +265,7 @@ class Identity(models.Model):
|
||||||
del headers["(request-target)"]
|
del headers["(request-target)"]
|
||||||
headers[
|
headers[
|
||||||
"Signature"
|
"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:
|
async with httpx.AsyncClient() as client:
|
||||||
return await client.request(
|
return await client.request(
|
||||||
method,
|
method,
|
||||||
|
@ -288,6 +289,7 @@ class Identity(models.Model):
|
||||||
view = "/@{self.username}@{self.domain_id}/"
|
view = "/@{self.username}@{self.domain_id}/"
|
||||||
view_short = "/@{self.username}/"
|
view_short = "/@{self.username}/"
|
||||||
actor = "{view}actor/"
|
actor = "{view}actor/"
|
||||||
|
key = "{actor}#main-key"
|
||||||
inbox = "{actor}inbox/"
|
inbox = "{actor}inbox/"
|
||||||
outbox = "{actor}outbox/"
|
outbox = "{actor}outbox/"
|
||||||
activate = "{view}activate/"
|
activate = "{view}activate/"
|
||||||
|
|
|
@ -133,7 +133,7 @@ class Actor(View):
|
||||||
"inbox": identity.urls.inbox.full(),
|
"inbox": identity.urls.inbox.full(),
|
||||||
"preferredUsername": identity.username,
|
"preferredUsername": identity.username,
|
||||||
"publicKey": {
|
"publicKey": {
|
||||||
"id": identity.urls.actor.full() + "#main-key",
|
"id": identity.urls.key.full(),
|
||||||
"owner": identity.urls.actor.full(),
|
"owner": identity.urls.actor.full(),
|
||||||
"publicKeyPem": identity.public_key,
|
"publicKeyPem": identity.public_key,
|
||||||
},
|
},
|
||||||
|
@ -181,7 +181,7 @@ class Inbox(View):
|
||||||
print(headers_string)
|
print(headers_string)
|
||||||
print(document)
|
print(document)
|
||||||
# Find the Identity by the actor on the incoming item
|
# 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:
|
if not identity.public_key:
|
||||||
# See if we can fetch it right now
|
# See if we can fetch it right now
|
||||||
async_to_sync(identity.fetch_actor)()
|
async_to_sync(identity.fetch_actor)()
|
||||||
|
|
Loading…
Reference in a new issue