Skip to content

Commit

Permalink
Support sending x509 certificates with gemini
Browse files Browse the repository at this point in the history
Fix an issue with gemtext renreding

[appimage]
[flatpak]
[docker-x11]
[deploy]
  • Loading branch information
cipres authored and cipres committed May 4, 2022
1 parent 09b282f commit d17439d
Show file tree
Hide file tree
Showing 11 changed files with 412 additions and 12 deletions.
52 changes: 46 additions & 6 deletions galacteek/browser/schemes/gemini/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import re
import ignition
from yarl import URL
from pathlib import Path

from PyQt5.QtCore import QUrl

Expand All @@ -10,23 +11,47 @@
from galacteek.browser.schemes import SCHEME_GEMINI

from .gemtext import gemTextToHtml
from .x509 import x509SelfSignedGenerate


class GeminiError(Exception):
pass


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):
"""
Expand All @@ -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()
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 0 additions & 2 deletions galacteek/browser/schemes/gemini/gemtext.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
59 changes: 59 additions & 0 deletions galacteek/browser/schemes/gemini/x509.py
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions galacteek/ld/ontolochain/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
25 changes: 22 additions & 3 deletions galacteek/templates/gemini_capsule_render.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,62 @@
<meta charset="UTF-8">
<title>{{ title }} </title>
<style>
@font-face {
font-family: 'Symbola';
font-style: normal;
src: url('qrc:/share/static/fonts/Symbola.ttf') format('ttf');
}

body {
background-color: #323232;
font: 12pt "Montserrat";
font: 14pt "Symbola";
margin: 5px;
color: white;
}

a {
color: #fe921f;
text-decoration: underline;
font-size: 14pt;
font-size: 1.1em;
}

a:hover {
color: #ffffff;
color: steelblue;
font-size: 1.2em;
}

h1 {
color: #4a9ea1;
padding: 10px;
font-size: 1.6em;
}
h2 {
color: #4a9fa1;
padding: 10px;
font-size: 1.4em;
}
h3 {
color: #4a9ba1;
padding: 10px;
font-size: 1.3em;
}

p {
color: #A3E658;
}

pre {
color: lightsteelblue;
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
word-wrap: break-word;
overflow-x: auto;
tab-width: 4;
font-family: monospace;
font-size: 0.9em;
margin: 0;
}
</style>

Expand Down
1 change: 1 addition & 0 deletions galacteek/ui/galacteek.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
<file>../../share/static/fonts/Inter-UI-Regular.woff2</file>
<file>../../share/static/fonts/DejaVuSans.ttf</file>
<file>../../share/static/fonts/SegoeUI.woff2</file>
<file>../../share/static/fonts/Symbola.ttf</file>
<file>../../share/icons/chat.png</file>
<file>../../share/icons/camera.png</file>
<file>../../share/icons/chat-active.png</file>
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ frozendict==1.2
filelock>=3.0.1
gitpython>=2.1.11
html2text==2020.1.16
ignition-gemini==0.1.11
ipfshttpclient>=0.7.0
jinja2==3.0.1
json-traverse>=0.4
Expand All @@ -51,6 +52,7 @@ pycryptodomex==3.9.8
pygments>=2.3.1
pyld>=2.0.2
PyNaCl>=1.4.0
python-dateutil==2.8.2
python-magic>=0.4.15
python3-crdt>=1.0.3
py-multibase>=1.0.3
Expand All @@ -76,5 +78,4 @@ aiogeminipfs @ git+https://gitlab.com/galacteek/aiogeminipfs.git@ipfs#egg=aiogem
rdflib @ git+https://gitlab.com/galacteek/rdflib.git@ipfs#egg=rdflib
rdflib-jsonld @ git+https://gitlab.com/galacteek/rdflib-jsonld.git@galacteek#egg=rdflib-jsonld
galacteek-ld-web4 @ git+https://gitlab.com/galacteek/galacteek-ld-web4.git@master
ignition-gemini @ git+https://gitlab.com/cipres/ignition.git@galacteek
SPARQL-Burger @ git+https://gitlab.com/galacteek/SPARQL-Burger.git@master#egg=SPARQL-Burger
Binary file added share/static/fonts/Symbola.ttf
Binary file not shown.
93 changes: 93 additions & 0 deletions share/translations/galacteek_en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1237,6 +1237,99 @@ Path: {0}, nodes processed: {1}</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MediaPlayer</name>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="76"/>
<source>No media player support available on your system</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="82"/>
<source>Media player error (code: {0})</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="88"/>
<source>Fullscreen</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="92"/>
<source>Copy playlist&apos;s IPFS path to the clipboard</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="98"/>
<source>Load playlist from the clipboard</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="104"/>
<source>Cannot load playlist</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="109"/>
<source>Export to Turtle (text/turtle)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="116"/>
<source>A playlist with this name already exists</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="122"/>
<source>Remove media from playlist</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="128"/>
<source>Playlist</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="135"/>
<source>Unsaved playlist</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="142"/>
<source>Playlist name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="148"/>
<source>Pin playlist items (here)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="155"/>
<source>Clear playlist</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="162"/>
<source>Save playlist</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="169"/>
<source>Load playlist</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="176"/>
<source>Already queued in the current playlist</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../galacteek/ui/mediaplayer/__init__.py" line="181"/>
<source>No media in playlist</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MediaPlaylist</name>
<message>
Expand Down
Loading

0 comments on commit d17439d

Please sign in to comment.