diff --git a/galacteek/browser/schemes/gemini/__init__.py b/galacteek/browser/schemes/gemini/__init__.py index 1d214d32..fc9181a8 100644 --- a/galacteek/browser/schemes/gemini/__init__.py +++ b/galacteek/browser/schemes/gemini/__init__.py @@ -1,6 +1,7 @@ import re import ignition from yarl import URL +from pathlib import Path from PyQt5.QtCore import QUrl @@ -10,6 +11,7 @@ from galacteek.browser.schemes import SCHEME_GEMINI from .gemtext import gemTextToHtml +from .x509 import x509SelfSignedGenerate class GeminiError(Exception): @@ -17,16 +19,39 @@ class GeminiError(Exception): class GeminiClient: - def geminiRequest(self, url: str): + def geminiRequest(self, url: str, referer, certificate): # Run in the thread executor try: - response = ignition.request(url) - data = response.data() - return response, data + response = ignition.request( + url, + referer=referer, + ca_cert=certificate + ) + return response, response.data() except Exception as err: log.debug(f'Gemini request error for URL {url}: {err}') return None, None + def certificateForHost(self, certsPath: Path, host: str): + try: + hcPath = certsPath.joinpath(host) + hcPath.mkdir(parents=True, exist_ok=True) + + keyPath = hcPath.joinpath('ca.key') + certPath = hcPath.joinpath('ca.crt') + + if keyPath.is_file() and certPath.is_file(): + # TODO: load the cert here, is_file() is cheap .. + return certPath, keyPath + else: + return x509SelfSignedGenerate( + host, + keyDestPath=keyPath, + certDestPath=certPath + ) + except Exception: + return None, None + class GeminiSchemeHandler(BaseURLSchemeHandler, GeminiClient): """ @@ -43,6 +68,9 @@ def __init__(self, parent=None, noMutexes=False): str(self.app.geminiHostsLocation) ) + self.certStoreLocation = self.app.dataLocation.joinpath( + 'gemini').joinpath('identities') + async def handleRequest(self, request, uid): rUrl = request.requestUrl() rInitiator = request.initiator() @@ -68,11 +96,21 @@ async def handleRequest(self, request, uid): else: log.debug(f'{rMethod}: {url}') + # Get cert + cert = await self.app.loop.run_in_executor( + self.app.executor, + self.certificateForHost, + self.certStoreLocation, + host + ) + # Run the request in the app's executor response, data = await self.app.loop.run_in_executor( self.app.executor, self.geminiRequest, - url + url, + None, + cert ) if not response or not data: @@ -215,7 +253,9 @@ async def handleRequest(self, ipfsop, request, uid): response, data = await self.app.loop.run_in_executor( self.app.executor, self.geminiRequest, - url + url, + None, + None ) if not response or not data: diff --git a/galacteek/browser/schemes/gemini/gemtext.py b/galacteek/browser/schemes/gemini/gemtext.py index 3839092d..ad77068e 100644 --- a/galacteek/browser/schemes/gemini/gemtext.py +++ b/galacteek/browser/schemes/gemini/gemtext.py @@ -62,8 +62,6 @@ def gemTextToHtml(gmi: str): title = None for line in gmi.split('\n'): - line = line.strip() - if len(line): if line.startswith("```") or line.endswith("```"): preformat = not preformat diff --git a/galacteek/browser/schemes/gemini/x509.py b/galacteek/browser/schemes/gemini/x509.py new file mode 100644 index 00000000..456c46c7 --- /dev/null +++ b/galacteek/browser/schemes/gemini/x509.py @@ -0,0 +1,59 @@ +from pathlib import Path +import datetime + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.x509.oid import NameOID + + +def x509SelfSignedGenerate(commonName, + orgName='Gemini Org', + unitName='Default CA Deployment', + monthsValid=12 * 40, + keyDestPath: Path = None, + certDestPath: Path = None): + one_day = datetime.timedelta(1, 0, 0) + + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + backend=default_backend() + ) + + public_key = private_key.public_key() + builder = x509.CertificateBuilder() + builder = builder.subject_name(x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, commonName) + ])) + builder = builder.issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, commonName), + ])) + builder = builder.not_valid_before(datetime.datetime.today() - one_day) + builder = builder.not_valid_after( + datetime.datetime.today() + datetime.timedelta( + days=monthsValid * 30) + ) + builder = builder.serial_number(x509.random_serial_number()) + builder = builder.public_key(public_key) + certificate = builder.sign( + private_key=private_key, algorithm=hashes.SHA256(), + backend=default_backend() + ) + + if keyDestPath and certDestPath: + with open(str(keyDestPath), "wb") as f: + f.write(private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption() + )) + with open(certDestPath, "wb") as f: + f.write(certificate.public_bytes( + encoding=serialization.Encoding.PEM, + )) + + return certDestPath, keyDestPath + else: + return None, None diff --git a/galacteek/ld/ontolochain/__init__.py b/galacteek/ld/ontolochain/__init__.py index 577d0683..abd6a3c4 100644 --- a/galacteek/ld/ontolochain/__init__.py +++ b/galacteek/ld/ontolochain/__init__.py @@ -89,6 +89,9 @@ async def create(ipfsop, '@type': 'OntoloChain', '@id': str(chainId), 'peerId': peerId, + 'didCreator': { + '@id': str(ipid.didUriRef) + }, 'description': description, 'dateCreated': utcDatetimeIso(), 'verificationMethod': f'{ipid.did}#keys-1', diff --git a/galacteek/templates/gemini_capsule_render.html b/galacteek/templates/gemini_capsule_render.html index f67744e3..4b15d03c 100644 --- a/galacteek/templates/gemini_capsule_render.html +++ b/galacteek/templates/gemini_capsule_render.html @@ -4,9 +4,15 @@