Permalink
Browse files

Implemented basic AP mechanisms

  • Loading branch information...
yabirgb committed Feb 7, 2019
1 parent eaa127f commit 3d4b89040cb299d229b7b0e92950c1001f818649
@@ -32,6 +32,8 @@ aiohttp = "*"
peewee-db-evolve = "*"
cachetools = "*"
async-lru = "*"
pyld = "*"
imageio-ffmpeg = "*"

[requires]
python_version = "3.6"
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "8ac183c7406d3285f38461c445bb9dd7080cca172a5007d4c9035f01ac885eae"
"sha256": "2963d3eb6e7258e5c033e3de8d7cb8193cd3b7f66a9ca2e7876aa8d612f704a5"
},
"pipfile-spec": 6,
"requires": {
@@ -199,9 +199,21 @@
},
"imageio": {
"hashes": [
"sha256:16b8077bc8a5fa7a58b3e744f7ecbb156d8c088132df31e0f4f546c98de3514a"
"sha256:1a2bbbb7cd38161340fa3b14d806dfbf914abf3ee6fd4592af2afb87d049f209",
"sha256:42e65aadfc3d57a1043615c92bdf6319b67589e49a0aae2b985b82144aceacad"
],
"version": "==2.4.1"
"version": "==2.5.0"
},
"imageio-ffmpeg": {
"hashes": [
"sha256:14efef5c82fdc3d314d56cbb4a9302e478afd9e1bdc3da7a1f0a67f42a5e3ec8",
"sha256:2beacb11b121440cfd0bc0caf80da54a3729956002707251ab8e26d3e03933a4",
"sha256:646cc63da4b6a955de5e30fbc63edb7c5692ac94fc100f4c71a7c122506122c6",
"sha256:d0eda59e2243375ca442e9428b299892425faf73e2e5f173d79de4362661dc50",
"sha256:dd3d4380d6e413819fef9f64f184cdc7e59cba88094383727bcf7ea5bb1bed99"
],
"index": "pypi",
"version": "==0.2.0"
},
"itsdangerous": {
"hashes": [
@@ -388,6 +400,13 @@
"index": "pypi",
"version": "==2.6.1"
},
"pyld": {
"hashes": [
"sha256:0c37df9dae757ee56303c7918e0a2147ba0a754523783126b2ad5fdf24d7cefc"
],
"index": "pypi",
"version": "==1.0.4"
},
"python-magic": {
"hashes": [
"sha256:f2674dcfad52ae6c49d4803fa027809540b130db1dec928cfbb9240316831375",
@@ -71,6 +71,9 @@ class Follow(Activity):
class Reject(Activity):
type = "Reject"

class Undo(Activity):
type = "Undo"

class Accept(Activity):
type = "Accept"

@@ -83,5 +86,5 @@ class RsaSignature2017(Activity):
"Follow": Follow,
"Accept": Accept,
"Reject": Reject,
"RsaSignature2017": RsaSignature2017
"Undo": Undo
})
@@ -0,0 +1,40 @@
import json
import logging
from typing import (Dict, Any)
import aiohttp
import requests
from settings import BASE_URL
from models.user import UserProfile
from activityPub.data_signature import generate_signature, sign_headers, HTTPSignaturesAuthRequest
from activityPub.key import CryptoKey

log = logging.getLogger(__name__)


def push_to_remote_actor(target: UserProfile, body: Dict) -> Any:

"""
Send activity to target inbox
"""

# Create Key
k = CryptoKey(body["actor"])
k.new()
generate_signature(body, k)

data = json.dumps(body)
print(data)

auth = HTTPSignaturesAuthRequest(k)
r = requests.post(target.uris.inbox, json=body, auth = auth)
print(r.status_code)
#print(r.request.headers)
#print(r.content)
if r.status_code < 400:
return True

return False




@@ -8,14 +8,31 @@
from Crypto.Hash import SHA256
from Crypto.Signature import PKCS1_v1_5
from requests.auth import AuthBase
from pyld import jsonld

from src.activityPub.key import CryptoKey
from activityPub.key import CryptoKey


log = logging.getLogger(__name__)

def _build_signed_string(
signed_headers: str, method: str, path: str, headers: Any, body_digest: str
) -> str:
out = []

class HTTPSignaturesAuth(AuthBase):
for signed_header in signed_headers.split(" "):
if signed_header == "(request-target)":
out.append("(request-target): " + method.lower() + " " + path)
elif signed_header == "digest":
out.append("digest: " + body_digest)
else:
out.append(signed_header + ": " + headers[signed_header])
return "\n".join(out)

def sign_headers(dsf,fdfs,fdff,fdf):
pass

class HTTPSignaturesAuthRequest(AuthBase):

"""
Plugin for request to sign petitions
@@ -25,29 +42,9 @@ class HTTPSignaturesAuth(AuthBase):
def __init__(self, key: CryptoKey) -> None:
self.key = key

self.headers = ["(request-target)", "user-agent", "host", "date", "digest", "content-type"]

def _build_signed_string(self,
signed_headers: List,
method: str,
path: str,
headers: Any,
body_digest: str
) -> str:

production = []
self.headers = "(request-target) user-agent host date digest content-type"

for header in signed_headers:

if header == '(request-target)':
production.append('(request-target): ' + method.lower() + ' ' + path)
elif header == 'digest'
production.append('digest: ' + body_digest)
else:
production.append(header + ': ' + headers[header])


return '\n'.join(production)

def __call__(self, r):

@@ -76,16 +73,16 @@ def __call__(self, r):

r.headers.update(new_headers)

hstring = " ".join(self.headers)

to_sign = self._build_signed_string(
to_sign = _build_signed_string(
self.headers,
r.method,
r.path_url,
r.headers,
digest
)

#print(to_sign)

signer = PKCS1_v1_5.new(self.key.privkey)

# Digest of the headers
@@ -97,10 +94,54 @@ def __call__(self, r):
key_id = self.key.key_id()

headers = {
"Signature": f'keyId="{key_id}",algorithm="rsa-sha256",headers="{hstring}",signature="{sig}"'
"Signature": f'keyId="{key_id}",algorithm="rsa-sha256",headers="{self.headers}",signature="{sig}"'
}

log.debug(f'Signed request headers {headers}')

r.headers.update(headers)
return r
return r

# Manage RSA signatures

def _options_hash(doc):
doc = dict(doc["signature"])
for k in ["type", "id", "signatureValue"]:
if k in doc:
del doc[k]
doc["@context"] = "https://w3id.org/identity/v1"
normalized = jsonld.normalize(
doc, {"algorithm": "URDNA2015", "format": "application/nquads"}
)
h = hashlib.new("sha256")
h.update(normalized.encode("utf-8"))
return h.hexdigest()


def _doc_hash(doc):
doc = dict(doc)
if "signature" in doc:
del doc["signature"]
normalized = jsonld.normalize(
doc, {"algorithm": "URDNA2015", "format": "application/nquads"}
)
h = hashlib.new("sha256")
h.update(normalized.encode("utf-8"))
return h.hexdigest()

def generate_signature(doc: Dict, key: 'Key'):

options = {
'type': 'RsaSignature2017',
'creator': doc['actor'] + '#main-key',
'created': datetime.utcnow().replace(microsecond=0).isoformat() + "Z"
}

doc["signature"] = options
to_be_signed = _options_hash(doc) + _doc_hash(doc)
signer = PKCS1_v1_5.new(key.privkey)
digest = SHA256.new()
digest.update(to_be_signed.encode("utf-8"))
sig = base64.b64encode(signer.sign(digest))
options["signatureValue"] = sig.decode("utf-8")
doc["signature"] = options
@@ -3,7 +3,7 @@
import requests
import logging
from urllib.parse import urlparse

from typing import (Any)
from settings import DOMAIN

#ActivityPub
@@ -44,7 +44,7 @@ def dereference(self):

return as_activitystream(res.json())

def get_or_create_remote_user(self):
def get_or_create_remote_user(self) -> UserProfile:
"""
Returns an instance of User after looking for it using it's ap_id
"""
@@ -76,7 +76,7 @@ def _local_uri(self, uri):
return uri == DOMAIN


def uri_to_resource(self, klass):
def uri_to_resource(self, klass) -> Any:

if self._local_uri(self.uri):
if klass.__name__ == 'User':
@@ -0,0 +1,61 @@
import base64
from typing import (Any, Dict, Optional)

from Crypto.PublicKey import RSA
from Crypto.Util import number

from database import DATABASE
from keys import import_keys


class CryptoKey:

DEFAULT_KEY_SIZE = 2048

def __init__(self, owner: str, id_: Optional[str]=None) -> None:
self.owner = owner
self.privkey_pem: Optional[str] = None
self.pubkey_pem: Optional[str] = None
self.privkey: Optional[Any] = None
self.pubkey: Optional[Any] = None
self.id_ = id_

def new(self) -> None:
self.load(import_keys()["actorKeys"]["privateKey"])

def key_id(self) -> str:
return f'{self.owner}#main-key'

def load_pub(self, pubkey_pem: str) -> None:
self.pubkey_pem = pubkey_pem
self.pubkey = RSA.importKey(pubkey_pem)

def load(self, privkey_pem: str) -> None:
self.privkey_pem = privkey_pem
self.privkey = RSA.importKey(self.privkey_pem)
self.pubkey_pem = self.privkey.publickey().exportKey("PEM").decode("utf-8")

def to_dict(self) -> Dict[str, Any]:
return {
"id": self.key_id(),
"owner": self.owner,
"publicKeyPem": self.pubkey_pem,
}

@classmethod
def from_dict(cls, data):
try:
k = cls(data["owner"], data["id"])
k.load_pub(data["publicKeyPem"])
except KeyError:
raise ValueError(f"bad key data {data!r}")
return k

def to_magic_key(self) -> str:
mod = base64.urlsafe_b64encode(
number.long_to_bytes(self.privkey.n) # type: ignore
).decode("utf-8")
pubexp = base64.urlsafe_b64encode(
number.long_to_bytes(self.privkey.e) # type: ignore
).decode("utf-8")
return f"data:application/magic-public-key,RSA.{mod}.{pubexp}"
@@ -16,7 +16,8 @@
from activityPub import activities
from activityPub.activities import as_activitystream

from api.v1.activityPub.methods import (store, handle_follow, handle_note)
from api.v1.activityPub.methods import (store, handle_note)
from tasks.ap_methods import handle_follow
from activityPub.activities.verbs import (Accept)

from activityPub.identity_manager import ActivityPubId
@@ -27,7 +28,7 @@

class Inbox(BaseHandler):

async def post(self, username):
def post(self, username):

#First we check the headers
#Lowercase them to ensure all have the same name
@@ -55,7 +56,7 @@ class Inbox(BaseHandler):
result = False
if activity.type == 'Follow':
logging.info(f"Starting follow process for {activity.object}" )
result = await handle_follow(activity)
result = handle_follow(activity)
print(result)
self.set_status(200)
elif activity.type == 'Accept':
@@ -64,6 +65,8 @@ class Inbox(BaseHandler):
elif activity.type == 'Create':
#result = handle_create(activity)
pass
elif activity.type == 'Delete':
pass

#store(activity, user, remote = True)
#self.set_status(500)
Oops, something went wrong.

0 comments on commit 3d4b890

Please sign in to comment.