import json
|
|
from base64 import b64encode, b64decode
|
|
from pathlib import Path
|
|
|
|
import xdgappdirs
|
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
from cryptography.hazmat.primitives import serialization
|
|
from jwcrypto import jwk
|
|
from pathvalidate import sanitize_filename
|
|
|
|
data_dir = xdgappdirs.user_data_dir('pyjod', as_path=True)
|
|
|
|
|
|
class RSAPrivateKey:
|
|
def __init__(self, key):
|
|
self.key = key
|
|
|
|
@classmethod
|
|
def generate(cls):
|
|
key = rsa.generate_private_key(65537, 2048)
|
|
return cls(key)
|
|
|
|
@classmethod
|
|
def from_pem(cls, data):
|
|
key = serialization.load_pem_private_key(data, None)
|
|
return cls(key)
|
|
|
|
@property
|
|
def jwk(self):
|
|
return jwk.JWK.from_pyca(self.key)
|
|
|
|
@property
|
|
def pubkey_b64(self):
|
|
pubkey = self.key.public_key()
|
|
pubkey_bytes = pubkey.public_bytes(
|
|
serialization.Encoding.DER,
|
|
serialization.PublicFormat.SubjectPublicKeyInfo
|
|
)
|
|
return b64encode(pubkey_bytes).decode('utf-8')
|
|
|
|
def to_pem(self):
|
|
return self.key.private_bytes(
|
|
serialization.Encoding.PEM,
|
|
serialization.PrivateFormat.TraditionalOpenSSL,
|
|
serialization.NoEncryption()
|
|
)
|
|
|
|
|
|
class RSAPublicKey:
|
|
def __init__(self, key):
|
|
self.key = key
|
|
|
|
@classmethod
|
|
def from_b64(cls, data):
|
|
key_bytes = b64decode(data)
|
|
key = serialization.load_der_public_key(key_bytes)
|
|
return cls(key)
|
|
|
|
@classmethod
|
|
def from_pem(cls, data):
|
|
key = serialization.load_pem_public_key(data)
|
|
return cls(key)
|
|
|
|
@property
|
|
def jwk(self):
|
|
return jwk.JWK.from_pyca(self.key)
|
|
|
|
def to_pem(self):
|
|
return self.key.public_bytes(
|
|
serialization.Encoding.PEM,
|
|
serialization.PublicFormat.SubjectPublicKeyInfo
|
|
)
|
|
|
|
|
|
class AppData:
|
|
def __init__(self, profile_name):
|
|
self.profile_dir = data_dir / sanitize_filename(profile_name)
|
|
self.profile_dir.mkdir(exist_ok=True, parents=True)
|
|
|
|
self.values_file = self.profile_dir / "values.json"
|
|
|
|
@property
|
|
def app_privkey(self):
|
|
key_file = self.profile_dir / 'app_privkey.pem'
|
|
if key_file.is_file():
|
|
with key_file.open('rb') as f:
|
|
key_bytes = f.read()
|
|
return RSAPrivateKey.from_pem(key_bytes)
|
|
else:
|
|
key = RSAPrivateKey.generate()
|
|
with key_file.open('wb') as f:
|
|
f.write(key.to_pem())
|
|
return key
|
|
|
|
@property
|
|
def serv_pubkey(self):
|
|
key_file = self.profile_dir / 'serv_pubkey.pem'
|
|
if key_file.is_file():
|
|
with key_file.open('rb') as f:
|
|
key_bytes = f.read()
|
|
return RSAPublicKey.from_pem(key_bytes)
|
|
else:
|
|
return None
|
|
|
|
@serv_pubkey.setter
|
|
def serv_pubkey(self, key_b64):
|
|
key = RSAPublicKey.from_b64(key_b64)
|
|
key_file = self.profile_dir / 'serv_pubkey.pem'
|
|
with key_file.open('wb') as f:
|
|
f.write(key.to_pem())
|
|
|
|
def __getitem__(self, key):
|
|
if self.values_file.is_file():
|
|
with self.values_file.open() as f:
|
|
values = json.load(f)
|
|
if key in values:
|
|
return values[key]
|
|
return None
|
|
|
|
def __setitem__(self, key, value):
|
|
if self.values_file.is_file():
|
|
with self.values_file.open() as f:
|
|
values = json.load(f)
|
|
else:
|
|
values = {}
|
|
values[key] = value
|
|
with self.values_file.open('w') as f:
|
|
json.dump(values, f)
|