switch to onion v3 ids
This commit is contained in:
parent
e175c72a40
commit
71634c9ed1
10 changed files with 212 additions and 120 deletions
|
@ -297,7 +297,7 @@ class Changelog(db.Model):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def action_addpeer(self, user, timestamp, peerid, username):
|
def action_addpeer(self, user, timestamp, peerid, username):
|
||||||
if len(peerid) == 16:
|
if len(peerid) == settings.ID_LENGTH:
|
||||||
from user.models import User
|
from user.models import User
|
||||||
if not 'users' in user.info:
|
if not 'users' in user.info:
|
||||||
user.info['users'] = {}
|
user.info['users'] = {}
|
||||||
|
@ -318,7 +318,7 @@ class Changelog(db.Model):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def action_editpeer(self, user, timestamp, peerid, data):
|
def action_editpeer(self, user, timestamp, peerid, data):
|
||||||
if len(peerid) == 16:
|
if len(peerid) == settings.ID_LENGTH:
|
||||||
from user.models import User
|
from user.models import User
|
||||||
peer = User.get_or_create(peerid)
|
peer = User.get_or_create(peerid)
|
||||||
update = False
|
update = False
|
||||||
|
@ -466,7 +466,7 @@ class Changelog(db.Model):
|
||||||
elif op == 'addpeer':
|
elif op == 'addpeer':
|
||||||
peer_id = data[1]
|
peer_id = data[1]
|
||||||
username = data[2]
|
username = data[2]
|
||||||
if len(peer_id) == 16:
|
if len(peer_id) == settings.ID_LENGTH:
|
||||||
peer = User.get(peer_id)
|
peer = User.get(peer_id)
|
||||||
if peer:
|
if peer:
|
||||||
username = peer.json().get('username', 'anonymous')
|
username = peer.json().get('username', 'anonymous')
|
||||||
|
|
|
@ -153,7 +153,7 @@ class Peer(object):
|
||||||
self.info['lists'][name] = list(set(self.info['lists'][name]) - set(ids))
|
self.info['lists'][name] = list(set(self.info['lists'][name]) - set(ids))
|
||||||
elif action == 'addpeer':
|
elif action == 'addpeer':
|
||||||
peerid, username = args
|
peerid, username = args
|
||||||
if len(peerid) == 16:
|
if len(peerid) == settings.ID_LENGTH:
|
||||||
self.info['peers'][peerid] = {'username': username}
|
self.info['peers'][peerid] = {'username': username}
|
||||||
# fixme, just trigger peer update here
|
# fixme, just trigger peer update here
|
||||||
from user.models import User
|
from user.models import User
|
||||||
|
@ -164,7 +164,7 @@ class Peer(object):
|
||||||
peer.save()
|
peer.save()
|
||||||
elif action == 'editpeer':
|
elif action == 'editpeer':
|
||||||
peerid, data = args
|
peerid, data = args
|
||||||
if len(peerid) == 16:
|
if len(peerid) == settings.ID_LENGTH:
|
||||||
if peerid not in self.info['peers']:
|
if peerid not in self.info['peers']:
|
||||||
self.info['peers'][peerid] = {}
|
self.info['peers'][peerid] = {}
|
||||||
for key in ('username', 'contact'):
|
for key in ('username', 'contact'):
|
||||||
|
|
|
@ -12,18 +12,20 @@ import socket
|
||||||
import socketserver
|
import socketserver
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from Crypto.PublicKey import RSA
|
|
||||||
from Crypto.Util.asn1 import DerSequence
|
|
||||||
from OpenSSL.crypto import dump_privatekey, FILETYPE_ASN1
|
|
||||||
from OpenSSL.SSL import (
|
from OpenSSL.SSL import (
|
||||||
Context, Connection, TLSv1_2_METHOD,
|
Connection,
|
||||||
VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT, VERIFY_CLIENT_ONCE
|
Context,
|
||||||
|
TLSv1_2_METHOD,
|
||||||
|
VERIFY_CLIENT_ONCE,
|
||||||
|
VERIFY_FAIL_IF_NO_PEER_CERT,
|
||||||
|
VERIFY_PEER,
|
||||||
)
|
)
|
||||||
|
|
||||||
import db
|
import db
|
||||||
import settings
|
import settings
|
||||||
import state
|
import state
|
||||||
import user
|
import user
|
||||||
|
import utils
|
||||||
from changelog import changelog_size, changelog_path
|
from changelog import changelog_size, changelog_path
|
||||||
from websocket import trigger_event
|
from websocket import trigger_event
|
||||||
|
|
||||||
|
@ -34,16 +36,15 @@ import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_service_id(key):
|
def get_service_id(connection):
|
||||||
'''
|
certs = connection.get_peer_cert_chain()
|
||||||
service_id is the first half of the sha1 of the rsa public key encoded in base32
|
for cert in certs:
|
||||||
'''
|
if cert.get_signature_algorithm().decode() == "ED25519":
|
||||||
# compute sha1 of public key and encode first half in base32
|
pubkey = cert.get_pubkey()
|
||||||
pub_der = DerSequence()
|
public_key = pubkey.to_cryptography_key().public_bytes_raw()
|
||||||
pub_der.decode(dump_privatekey(FILETYPE_ASN1, key))
|
service_id = utils.get_onion(public_key)
|
||||||
public_key = RSA.construct((pub_der._seq[1], pub_der._seq[2])).exportKey('DER')[22:]
|
|
||||||
service_id = base64.b32encode(hashlib.sha1(public_key).digest()[:10]).lower().decode()
|
|
||||||
return service_id
|
return service_id
|
||||||
|
raise Exception("connection with invalid certificate")
|
||||||
|
|
||||||
class TLSTCPServer(socketserver.TCPServer):
|
class TLSTCPServer(socketserver.TCPServer):
|
||||||
|
|
||||||
|
@ -55,7 +56,7 @@ class TLSTCPServer(socketserver.TCPServer):
|
||||||
socketserver.TCPServer.__init__(self, server_address, HandlerClass)
|
socketserver.TCPServer.__init__(self, server_address, HandlerClass)
|
||||||
ctx = Context(TLSv1_2_METHOD)
|
ctx = Context(TLSv1_2_METHOD)
|
||||||
ctx.use_privatekey_file(settings.ssl_key_path)
|
ctx.use_privatekey_file(settings.ssl_key_path)
|
||||||
ctx.use_certificate_file(settings.ssl_cert_path)
|
ctx.use_certificate_chain_file(settings.ssl_cert_path)
|
||||||
# only allow clients with cert:
|
# only allow clients with cert:
|
||||||
ctx.set_verify(VERIFY_PEER | VERIFY_CLIENT_ONCE | VERIFY_FAIL_IF_NO_PEER_CERT, self._accept)
|
ctx.set_verify(VERIFY_PEER | VERIFY_CLIENT_ONCE | VERIFY_FAIL_IF_NO_PEER_CERT, self._accept)
|
||||||
#ctx.set_verify(VERIFY_PEER | VERIFY_CLIENT_ONCE, self._accept)
|
#ctx.set_verify(VERIFY_PEER | VERIFY_CLIENT_ONCE, self._accept)
|
||||||
|
@ -111,8 +112,7 @@ class Handler(http.server.SimpleHTTPRequestHandler):
|
||||||
return self.do_GET()
|
return self.do_GET()
|
||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
#x509 = self.connection.get_peer_certificate()
|
user_id = get_service_id(self.connection)
|
||||||
#user_id = get_service_id(x509.get_pubkey()) if x509 else None
|
|
||||||
import item.models
|
import item.models
|
||||||
parts = self.path.split('/')
|
parts = self.path.split('/')
|
||||||
if len(parts) == 3 and parts[1] in ('get', 'preview'):
|
if len(parts) == 3 and parts[1] in ('get', 'preview'):
|
||||||
|
@ -185,8 +185,7 @@ class Handler(http.server.SimpleHTTPRequestHandler):
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
def _changelog(self):
|
def _changelog(self):
|
||||||
x509 = self.connection.get_peer_certificate()
|
user_id = get_service_id(self.connection)
|
||||||
user_id = get_service_id(x509.get_pubkey()) if x509 else None
|
|
||||||
with db.session():
|
with db.session():
|
||||||
u = user.models.User.get(user_id)
|
u = user.models.User.get(user_id)
|
||||||
if not u:
|
if not u:
|
||||||
|
@ -257,8 +256,7 @@ class Handler(http.server.SimpleHTTPRequestHandler):
|
||||||
|
|
||||||
ping responds public ip
|
ping responds public ip
|
||||||
'''
|
'''
|
||||||
x509 = self.connection.get_peer_certificate()
|
user_id = get_service_id(self.connection)
|
||||||
user_id = get_service_id(x509.get_pubkey()) if x509 else None
|
|
||||||
|
|
||||||
content = {}
|
content = {}
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -24,9 +24,11 @@ if not os.path.exists(data_path):
|
||||||
|
|
||||||
db_path = os.path.join(data_path, 'data.db')
|
db_path = os.path.join(data_path, 'data.db')
|
||||||
log_path = os.path.join(data_path, 'debug.log')
|
log_path = os.path.join(data_path, 'debug.log')
|
||||||
ssl_cert_path = os.path.join(data_path, 'node.ssl.crt')
|
|
||||||
ssl_key_path = os.path.join(data_path, 'tor', 'private_key')
|
|
||||||
|
|
||||||
|
ca_key_path = os.path.join(data_path, 'node.ca.key')
|
||||||
|
ca_cert_path = os.path.join(data_path, 'node.ca.crt')
|
||||||
|
ssl_cert_path = os.path.join(data_path, 'node.tls.crt')
|
||||||
|
ssl_key_path = os.path.join(data_path, 'node.tls.key')
|
||||||
|
|
||||||
if os.path.exists(oml_data_path):
|
if os.path.exists(oml_data_path):
|
||||||
with open(oml_data_path) as fd:
|
with open(oml_data_path) as fd:
|
||||||
|
@ -57,7 +59,7 @@ for key in server_defaults:
|
||||||
|
|
||||||
release = pdict(os.path.join(data_path, 'release.json'))
|
release = pdict(os.path.join(data_path, 'release.json'))
|
||||||
|
|
||||||
USER_ID = get_user_id(ssl_key_path, ssl_cert_path)
|
USER_ID = get_user_id(ssl_key_path, ssl_cert_path, ca_key_path, ca_cert_path)
|
||||||
|
|
||||||
OML_UPDATE_KEY = 'K55EZpPYbP3X+3mA66cztlw1sSaUMqGwfTDKQyP2qOU'
|
OML_UPDATE_KEY = 'K55EZpPYbP3X+3mA66cztlw1sSaUMqGwfTDKQyP2qOU'
|
||||||
OML_UPDATE_CERT = '''-----BEGIN CERTIFICATE-----
|
OML_UPDATE_CERT = '''-----BEGIN CERTIFICATE-----
|
||||||
|
@ -96,3 +98,5 @@ if not FULLTEXT_SUPPORT:
|
||||||
config['itemKeys'] = [k for k in config['itemKeys'] if k['id'] != 'fulltext']
|
config['itemKeys'] = [k for k in config['itemKeys'] if k['id'] != 'fulltext']
|
||||||
|
|
||||||
DB_VERSION = 20
|
DB_VERSION = 20
|
||||||
|
|
||||||
|
ID_LENGTH = 56
|
||||||
|
|
17
oml/tor.py
17
oml/tor.py
|
@ -22,6 +22,7 @@ import logging
|
||||||
logging.getLogger('stem').setLevel(logging.ERROR)
|
logging.getLogger('stem').setLevel(logging.ERROR)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TorDaemon(Thread):
|
class TorDaemon(Thread):
|
||||||
installing = False
|
installing = False
|
||||||
running = True
|
running = True
|
||||||
|
@ -201,18 +202,20 @@ class Tor(object):
|
||||||
return False
|
return False
|
||||||
controller = self.controller
|
controller = self.controller
|
||||||
if controller.get_version() >= stem.version.Requirement.ADD_ONION:
|
if controller.get_version() >= stem.version.Requirement.ADD_ONION:
|
||||||
with open(settings.ssl_key_path, 'rb') as fd:
|
private_key, public_key = utils.load_pem_key(settings.ca_key_path)
|
||||||
private_key = fd.read()
|
key_type, key_content = utils.get_onion_key(private_key)
|
||||||
key_content = RSA.importKey(private_key).exportKey().decode()
|
|
||||||
key_content = ''.join(key_content.strip().split('\n')[1:-1])
|
|
||||||
ports = {9851: settings.server['node_port']}
|
ports = {9851: settings.server['node_port']}
|
||||||
if settings.preferences.get('enableReadOnlyService'):
|
if settings.preferences.get('enableReadOnlyService'):
|
||||||
ports[80] = settings.server['public_port']
|
ports[80] = settings.server['public_port']
|
||||||
controller.remove_ephemeral_hidden_service(settings.USER_ID)
|
controller.remove_ephemeral_hidden_service(settings.USER_ID)
|
||||||
response = controller.create_ephemeral_hidden_service(ports,
|
response = controller.create_ephemeral_hidden_service(
|
||||||
key_type='RSA1024', key_content=key_content,
|
ports,
|
||||||
detached=True)
|
key_type=key_type, key_content=key_content,
|
||||||
|
detached=True
|
||||||
|
)
|
||||||
if response.is_ok():
|
if response.is_ok():
|
||||||
|
if response.service_id != settings.USER_ID:
|
||||||
|
logger.error("Something is wrong with tor id %s vs %s", response.service_id, settings.USER_ID)
|
||||||
logger.debug('published node as https://%s.onion:%s',
|
logger.debug('published node as https://%s.onion:%s',
|
||||||
settings.USER_ID, settings.server_defaults['node_port'])
|
settings.USER_ID, settings.server_defaults['node_port'])
|
||||||
if settings.preferences.get('enableReadOnlyService'):
|
if settings.preferences.get('enableReadOnlyService'):
|
||||||
|
|
|
@ -66,27 +66,30 @@ class TorHTTPSConnection(http.client.HTTPSConnection):
|
||||||
def __init__(self, host, port=None, service_id=None, check_hostname=None, context=None, **kwargs):
|
def __init__(self, host, port=None, service_id=None, check_hostname=None, context=None, **kwargs):
|
||||||
self._service_id = service_id
|
self._service_id = service_id
|
||||||
if self._service_id:
|
if self._service_id:
|
||||||
if hasattr(ssl, '_create_default_https_context'):
|
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
||||||
context = ssl._create_default_https_context()
|
|
||||||
elif hasattr(ssl, '_create_stdlib_context'):
|
|
||||||
context = ssl._create_stdlib_context()
|
|
||||||
if context:
|
if context:
|
||||||
context.check_hostname = False
|
context.check_hostname = False
|
||||||
context.verify_mode = ssl.CERT_NONE
|
context.verify_mode = ssl.CERT_NONE
|
||||||
# tor keys are still 1024 bit, debian started to require 2048 by default,
|
|
||||||
# try to lower requirements to 1024 if needed
|
|
||||||
try:
|
|
||||||
context.load_cert_chain(settings.ssl_cert_path, settings.ssl_key_path)
|
|
||||||
except ssl.SSLError:
|
|
||||||
context.set_ciphers('DEFAULT@SECLEVEL=1')
|
|
||||||
context.load_cert_chain(settings.ssl_cert_path, settings.ssl_key_path)
|
context.load_cert_chain(settings.ssl_cert_path, settings.ssl_key_path)
|
||||||
context.load_default_certs()
|
context.load_default_certs()
|
||||||
|
context.set_alpn_protocols(['http/1.1'])
|
||||||
|
context.post_handshake_auth = True
|
||||||
http.client.HTTPSConnection.__init__(self, host, port,
|
http.client.HTTPSConnection.__init__(self, host, port,
|
||||||
check_hostname=check_hostname, context=context, **kwargs)
|
check_hostname=check_hostname, context=context, **kwargs)
|
||||||
|
|
||||||
if not is_local(host):
|
if not is_local(host):
|
||||||
self._create_connection = create_tor_connection
|
self._create_connection = create_tor_connection
|
||||||
|
|
||||||
|
def get_service_id_cert(self):
|
||||||
|
for cert in self.sock._sslobj.get_verified_chain():
|
||||||
|
info = cert.get_info()
|
||||||
|
subject = info.get("subject")
|
||||||
|
if subject:
|
||||||
|
CN = subject[0][0][1]
|
||||||
|
if CN == self._service_id:
|
||||||
|
cert = cert.public_bytes()
|
||||||
|
return cert
|
||||||
|
|
||||||
def _check_service_id(self, cert):
|
def _check_service_id(self, cert):
|
||||||
service_id = get_service_id(cert=cert)
|
service_id = get_service_id(cert=cert)
|
||||||
if service_id != self._service_id:
|
if service_id != self._service_id:
|
||||||
|
@ -96,11 +99,9 @@ class TorHTTPSConnection(http.client.HTTPSConnection):
|
||||||
def connect(self):
|
def connect(self):
|
||||||
http.client.HTTPSConnection.connect(self)
|
http.client.HTTPSConnection.connect(self)
|
||||||
if self._service_id:
|
if self._service_id:
|
||||||
cert = self.sock.getpeercert(binary_form=True)
|
cert = self.get_service_id_cert()
|
||||||
if not self._check_service_id(cert):
|
if not self._check_service_id(cert):
|
||||||
raise InvalidCertificateException(self._service_id, cert,
|
raise InvalidCertificateException(self._service_id, cert, 'service_id mismatch')
|
||||||
'service_id mismatch')
|
|
||||||
#logger.debug('CIPHER %s VERSION %s', self.sock.cipher(), self.sock.ssl_version)
|
|
||||||
|
|
||||||
class TorHTTPSHandler(urllib.request.HTTPSHandler):
|
class TorHTTPSHandler(urllib.request.HTTPSHandler):
|
||||||
def __init__(self, debuglevel=0, context=None, check_hostname=None, service_id=None):
|
def __init__(self, debuglevel=0, context=None, check_hostname=None, service_id=None):
|
||||||
|
|
|
@ -411,7 +411,7 @@ def requestPeering(data):
|
||||||
nickname (optional)
|
nickname (optional)
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
if len(data.get('id', '')) != 16:
|
if len(data.get('id', '')) != settings.ID_LENGTH:
|
||||||
logger.debug('invalid user id')
|
logger.debug('invalid user id')
|
||||||
return {}
|
return {}
|
||||||
u = models.User.get_or_create(data['id'])
|
u = models.User.get_or_create(data['id'])
|
||||||
|
@ -434,7 +434,7 @@ def acceptPeering(data):
|
||||||
message
|
message
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
if len(data.get('id', '')) != 16:
|
if len(data.get('id', '')) != settings.ID_LENGTH:
|
||||||
logger.debug('invalid user id')
|
logger.debug('invalid user id')
|
||||||
return {}
|
return {}
|
||||||
logger.debug('acceptPeering... %s', data)
|
logger.debug('acceptPeering... %s', data)
|
||||||
|
@ -453,8 +453,8 @@ def rejectPeering(data):
|
||||||
message
|
message
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
if len(data.get('id', '')) not in (16, 43):
|
if len(data.get('id', '')) not in (16, 43, 56):
|
||||||
logger.debug('invalid user id')
|
logger.debug('invalid user id: %s', data)
|
||||||
return {}
|
return {}
|
||||||
u = models.User.get_or_create(data['id'])
|
u = models.User.get_or_create(data['id'])
|
||||||
u.info['message'] = data.get('message', '')
|
u.info['message'] = data.get('message', '')
|
||||||
|
@ -471,8 +471,8 @@ def removePeering(data):
|
||||||
message
|
message
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
if len(data.get('id', '')) not in (16, 43):
|
if len(data.get('id', '')) not in (16, 43, 56):
|
||||||
logger.debug('invalid user id')
|
logger.debug('invalid user id: %s', data)
|
||||||
return {}
|
return {}
|
||||||
u = models.User.get(data['id'], for_update=True)
|
u = models.User.get(data['id'], for_update=True)
|
||||||
if u:
|
if u:
|
||||||
|
@ -488,8 +488,8 @@ def cancelPeering(data):
|
||||||
takes {
|
takes {
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
if len(data.get('id', '')) != 16:
|
if len(data.get('id', '')) != settings.ID_LENGTH:
|
||||||
logger.debug('invalid user id')
|
logger.debug('invalid user id: %s', data)
|
||||||
return {}
|
return {}
|
||||||
u = models.User.get_or_create(data['id'])
|
u = models.User.get_or_create(data['id'])
|
||||||
u.info['message'] = data.get('message', '')
|
u.info['message'] = data.get('message', '')
|
||||||
|
|
|
@ -27,7 +27,7 @@ class User(db.Model):
|
||||||
created = sa.Column(sa.DateTime())
|
created = sa.Column(sa.DateTime())
|
||||||
modified = sa.Column(sa.DateTime())
|
modified = sa.Column(sa.DateTime())
|
||||||
|
|
||||||
id = sa.Column(sa.String(43), primary_key=True)
|
id = sa.Column(sa.String(128), primary_key=True)
|
||||||
info = sa.Column(MutableDict.as_mutable(sa.PickleType(pickler=json_pickler)))
|
info = sa.Column(MutableDict.as_mutable(sa.PickleType(pickler=json_pickler)))
|
||||||
|
|
||||||
nickname = sa.Column(sa.String(256), index=True)
|
nickname = sa.Column(sa.String(256), index=True)
|
||||||
|
@ -256,7 +256,7 @@ class List(db.Model):
|
||||||
type = sa.Column(sa.String(64))
|
type = sa.Column(sa.String(64))
|
||||||
_query = sa.Column('query', MutableDict.as_mutable(sa.PickleType(pickler=json_pickler)))
|
_query = sa.Column('query', MutableDict.as_mutable(sa.PickleType(pickler=json_pickler)))
|
||||||
|
|
||||||
user_id = sa.Column(sa.String(43), sa.ForeignKey('user.id'))
|
user_id = sa.Column(sa.String(128), sa.ForeignKey('user.id'))
|
||||||
user = sa.orm.relationship('User', backref=sa.orm.backref('lists', lazy='dynamic'))
|
user = sa.orm.relationship('User', backref=sa.orm.backref('lists', lazy='dynamic'))
|
||||||
|
|
||||||
items = sa.orm.relationship('Item', secondary=list_items,
|
items = sa.orm.relationship('Item', secondary=list_items,
|
||||||
|
@ -456,7 +456,7 @@ class Metadata(db.Model):
|
||||||
|
|
||||||
id = sa.Column(sa.Integer(), primary_key=True)
|
id = sa.Column(sa.Integer(), primary_key=True)
|
||||||
item_id = sa.Column(sa.String(32))
|
item_id = sa.Column(sa.String(32))
|
||||||
user_id = sa.Column(sa.String(43), sa.ForeignKey('user.id'))
|
user_id = sa.Column(sa.String(128), sa.ForeignKey('user.id'))
|
||||||
data_hash = sa.Column(sa.String(40), index=True)
|
data_hash = sa.Column(sa.String(40), index=True)
|
||||||
data = sa.Column(MutableDict.as_mutable(sa.PickleType(pickler=json_pickler)))
|
data = sa.Column(MutableDict.as_mutable(sa.PickleType(pickler=json_pickler)))
|
||||||
|
|
||||||
|
|
198
oml/utils.py
198
oml/utils.py
|
@ -17,19 +17,26 @@ import time
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
import ox
|
import ox
|
||||||
|
import OpenSSL.crypto
|
||||||
from OpenSSL.crypto import (
|
from OpenSSL.crypto import (
|
||||||
load_privatekey, load_certificate,
|
dump_certificate,
|
||||||
dump_privatekey, dump_certificate,
|
dump_privatekey,
|
||||||
FILETYPE_ASN1, FILETYPE_PEM, PKey, TYPE_RSA,
|
FILETYPE_PEM,
|
||||||
X509, X509Extension
|
load_certificate,
|
||||||
|
load_privatekey,
|
||||||
|
PKey,
|
||||||
|
TYPE_RSA,
|
||||||
|
X509,
|
||||||
|
X509Extension
|
||||||
)
|
)
|
||||||
from Crypto.PublicKey import RSA
|
from cryptography.hazmat.primitives import serialization
|
||||||
from Crypto.Util.asn1 import DerSequence
|
from cryptography.hazmat.primitives.asymmetric import ed25519
|
||||||
|
|
||||||
|
|
||||||
from meta.utils import normalize_isbn, find_isbns, get_language, to_isbn13
|
from meta.utils import normalize_isbn, find_isbns, get_language, to_isbn13
|
||||||
from win32utils import get_short_path_name
|
from win32utils import get_short_path_name
|
||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
logging.getLogger('PIL').setLevel(logging.ERROR)
|
logging.getLogger('PIL').setLevel(logging.ERROR)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -92,7 +99,7 @@ def resize_image(data, width=None, size=None):
|
||||||
height = max(height, 1)
|
height = max(height, 1)
|
||||||
|
|
||||||
if width < source_width:
|
if width < source_width:
|
||||||
resize_method = Image.ANTIALIAS
|
resize_method = Image.LANCZOS
|
||||||
else:
|
else:
|
||||||
resize_method = Image.BICUBIC
|
resize_method = Image.BICUBIC
|
||||||
output = source.resize((width, height), resize_method)
|
output = source.resize((width, height), resize_method)
|
||||||
|
@ -119,78 +126,157 @@ def get_position_by_id(list, key):
|
||||||
return i
|
return i
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
def get_user_id(private_key, cert_path):
|
def sign_cert(cert, key):
|
||||||
if os.path.exists(private_key):
|
# pyOpenSSL sgin api does not allow NULL hash
|
||||||
with open(private_key) as fd:
|
# return cert.sign(key, None)
|
||||||
key = load_privatekey(FILETYPE_PEM, fd.read())
|
return OpenSSL.crypto._lib.X509_sign(cert._x509, key._pkey, OpenSSL.crypto._ffi.NULL)
|
||||||
if key.bits() != 1024:
|
|
||||||
os.unlink(private_key)
|
def load_pem_key(pem):
|
||||||
|
with open(pem) as fd:
|
||||||
|
ca_key_pem = fd.read()
|
||||||
|
key = load_privatekey(FILETYPE_PEM, ca_key_pem)
|
||||||
|
if key.bits() != 256:
|
||||||
|
raise Exception("Invalid key %s" % pem)
|
||||||
|
key = key.to_cryptography_key()
|
||||||
|
private_key = key.private_bytes_raw()
|
||||||
|
public_key = key.public_key().public_bytes_raw()
|
||||||
|
return private_key, public_key
|
||||||
|
|
||||||
|
|
||||||
|
def expand_private_key(secret_key) -> bytes:
|
||||||
|
hash = hashlib.sha512(secret_key).digest()
|
||||||
|
hash = bytearray(hash)
|
||||||
|
hash[0] &= 248
|
||||||
|
hash[31] &= 127
|
||||||
|
hash[31] |= 64
|
||||||
|
return bytes(hash)
|
||||||
|
|
||||||
|
def get_onion(pubkey):
|
||||||
|
version_byte = b"\x03"
|
||||||
|
checksum_str = ".onion checksum".encode()
|
||||||
|
checksum = hashlib.sha3_256(checksum_str + pubkey + version_byte).digest()[:2]
|
||||||
|
return base64.b32encode(pubkey + checksum + version_byte).decode().lower()
|
||||||
|
|
||||||
|
def get_onion_key(private_key):
|
||||||
|
onion_key = expand_private_key(private_key)
|
||||||
|
key_type = 'ED25519-V3'
|
||||||
|
key_content = base64.encodebytes(onion_key).decode().strip().replace('\n', '')
|
||||||
|
return key_type, key_content
|
||||||
|
|
||||||
|
def get_user_id(key_path, cert_path, ca_key_path, ca_cert_path):
|
||||||
|
if os.path.exists(ca_key_path):
|
||||||
|
try:
|
||||||
|
private_key, public_key = load_pem_key(ca_key_path)
|
||||||
|
except:
|
||||||
|
os.unlink(ca_key_path)
|
||||||
else:
|
else:
|
||||||
user_id = get_service_id(private_key)
|
user_id = get_onion(public_key)
|
||||||
if not os.path.exists(private_key):
|
|
||||||
if os.path.exists(cert_path):
|
if not os.path.exists(ca_key_path):
|
||||||
os.unlink(cert_path)
|
private_key = ed25519.Ed25519PrivateKey.generate()
|
||||||
folder = os.path.dirname(private_key)
|
private_bytes = private_key.private_bytes(
|
||||||
if not os.path.exists(folder):
|
encoding=serialization.Encoding.PEM,
|
||||||
os.makedirs(folder)
|
format=serialization.PrivateFormat.PKCS8,
|
||||||
os.chmod(folder, 0o700)
|
encryption_algorithm=serialization.NoEncryption()
|
||||||
key = PKey()
|
)
|
||||||
key.generate_key(TYPE_RSA, 1024)
|
with open(ca_key_path, 'wb') as fd:
|
||||||
with open(private_key, 'wb') as fd:
|
fd.write(private_bytes)
|
||||||
os.chmod(private_key, 0o600)
|
|
||||||
fd.write(dump_privatekey(FILETYPE_PEM, key))
|
public_key = private_key.public_key().public_bytes_raw()
|
||||||
os.chmod(private_key, 0o400)
|
user_id = get_onion(public_key)
|
||||||
user_id = get_service_id(private_key)
|
|
||||||
if not os.path.exists(cert_path) or \
|
if not os.path.exists(ca_cert_path) or \
|
||||||
(datetime.now() - datetime.fromtimestamp(os.path.getmtime(cert_path))).days > 60:
|
(datetime.now() - datetime.fromtimestamp(os.path.getmtime(ca_cert_path))).days > 5*365:
|
||||||
|
with open(ca_key_path, 'rb') as key_file:
|
||||||
|
key_data = key_file.read()
|
||||||
|
cakey = load_privatekey(FILETYPE_PEM, key_data)
|
||||||
ca = X509()
|
ca = X509()
|
||||||
ca.set_version(2)
|
ca.set_version(2)
|
||||||
ca.set_serial_number(1)
|
ca.set_serial_number(1)
|
||||||
ca.get_subject().CN = user_id
|
ca.get_subject().CN = user_id
|
||||||
ca.gmtime_adj_notBefore(0)
|
ca.gmtime_adj_notBefore(0)
|
||||||
ca.gmtime_adj_notAfter(90 * 24 * 60 * 60)
|
ca.gmtime_adj_notAfter(10 * 356 * 24 * 60 * 60)
|
||||||
ca.set_issuer(ca.get_subject())
|
ca.set_issuer(ca.get_subject())
|
||||||
ca.set_pubkey(key)
|
ca.set_pubkey(cakey)
|
||||||
ca.add_extensions([
|
ca.add_extensions([
|
||||||
X509Extension(b"basicConstraints", True, b"CA:TRUE, pathlen:0"),
|
X509Extension(b"basicConstraints", False, b"CA:TRUE"),
|
||||||
X509Extension(b"nsCertType", True, b"sslCA"),
|
X509Extension(b"keyUsage", False, b"keyCertSign, cRLSign"),
|
||||||
|
X509Extension(
|
||||||
|
b"subjectKeyIdentifier", False, b"hash", subject=ca
|
||||||
|
),
|
||||||
|
])
|
||||||
|
ca.add_extensions([
|
||||||
|
X509Extension(
|
||||||
|
b"authorityKeyIdentifier", False, b"keyid:always", issuer=ca
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
sign_cert(ca, cakey)
|
||||||
|
|
||||||
|
with open(ca_cert_path, 'wb') as fd:
|
||||||
|
fd.write(dump_certificate(FILETYPE_PEM, ca))
|
||||||
|
|
||||||
|
if os.path.exists(cert_path):
|
||||||
|
os.unlink(cert_path)
|
||||||
|
if os.path.exists(key_path):
|
||||||
|
os.unlink(key_path)
|
||||||
|
else:
|
||||||
|
with open(ca_cert_path) as fd:
|
||||||
|
ca = load_certificate(FILETYPE_PEM, fd.read())
|
||||||
|
with open(ca_key_path) as fd:
|
||||||
|
cakey = load_privatekey(FILETYPE_PEM, fd.read())
|
||||||
|
|
||||||
|
|
||||||
|
# create RSA intermediate certificate since clients don't quite like Ed25519 yet
|
||||||
|
if not os.path.exists(cert_path) or \
|
||||||
|
(datetime.now() - datetime.fromtimestamp(os.path.getmtime(cert_path))).days > 60:
|
||||||
|
|
||||||
|
key = PKey()
|
||||||
|
key.generate_key(TYPE_RSA, 2048)
|
||||||
|
|
||||||
|
cert = X509()
|
||||||
|
cert.set_version(2)
|
||||||
|
cert.set_serial_number(2)
|
||||||
|
cert.get_subject().CN = user_id + ".onion"
|
||||||
|
cert.gmtime_adj_notBefore(0)
|
||||||
|
cert.gmtime_adj_notAfter(90 * 24 * 60 * 60)
|
||||||
|
cert.set_issuer(ca.get_subject())
|
||||||
|
cert.set_pubkey(key)
|
||||||
|
subject_alt_names = b"DNS: %s.onion" % user_id.encode()
|
||||||
|
cert.add_extensions([
|
||||||
|
X509Extension(b"basicConstraints", True, b"CA:FALSE"),
|
||||||
X509Extension(b"extendedKeyUsage", True,
|
X509Extension(b"extendedKeyUsage", True,
|
||||||
b"serverAuth,clientAuth,emailProtection,timeStamping,msCodeInd,msCodeCom,msCTLSign,msSGC,msEFS,nsSGC"),
|
b"serverAuth,clientAuth,emailProtection,timeStamping,msCodeInd,msCodeCom,msCTLSign,msSGC,msEFS,nsSGC"),
|
||||||
X509Extension(b"keyUsage", False, b"keyCertSign, cRLSign"),
|
X509Extension(b"keyUsage", False, b"keyCertSign, cRLSign"),
|
||||||
X509Extension(b"subjectKeyIdentifier", False, b"hash", subject=ca),
|
X509Extension(b"subjectKeyIdentifier", False, b"hash", subject=ca),
|
||||||
|
X509Extension(b"subjectAltName", critical=True, value=subject_alt_names),
|
||||||
])
|
])
|
||||||
ca.sign(key, "sha256")
|
sign_cert(cert, cakey)
|
||||||
with open(cert_path, 'wb') as fd:
|
with open(cert_path, 'wb') as fd:
|
||||||
|
fd.write(dump_certificate(FILETYPE_PEM, cert))
|
||||||
fd.write(dump_certificate(FILETYPE_PEM, ca))
|
fd.write(dump_certificate(FILETYPE_PEM, ca))
|
||||||
|
with open(key_path, 'wb') as fd:
|
||||||
|
fd.write(dump_privatekey(FILETYPE_PEM, key))
|
||||||
return user_id
|
return user_id
|
||||||
|
|
||||||
|
|
||||||
def get_service_id(private_key_file=None, cert=None):
|
def get_service_id(private_key_file=None, cert=None):
|
||||||
'''
|
'''
|
||||||
service_id is the first half of the sha1 of the rsa public key encoded in base32
|
service_id is the first half of the sha1 of the rsa public key encoded in base32
|
||||||
'''
|
'''
|
||||||
if private_key_file:
|
if private_key_file:
|
||||||
with open(private_key_file, 'rb') as fd:
|
with open(private_key_file, 'rb') as key_file:
|
||||||
private_key = fd.read()
|
key_type, key_content = key_file.read().split(b':', 1)
|
||||||
public_key = RSA.importKey(private_key).publickey().exportKey('DER')[22:]
|
private_key = base64.decodebytes(key_content)
|
||||||
# compute sha1 of public key and encode first half in base32
|
public_key = Ed25519().public_key_from_hash(private_key)
|
||||||
service_id = base64.b32encode(hashlib.sha1(public_key).digest()[:10]).lower().decode()
|
service_id = get_onion(public_key)
|
||||||
'''
|
|
||||||
# compute public key from priate key and export in DER format
|
|
||||||
# ignoring the SPKI header(22 bytes)
|
|
||||||
key = load_privatekey(FILETYPE_PEM, private_key)
|
|
||||||
cert = X509()
|
|
||||||
cert.set_pubkey(key)
|
|
||||||
public_key = dump_privatekey(FILETYPE_ASN1, cert.get_pubkey())[22:]
|
|
||||||
# compute sha1 of public key and encode first half in base32
|
|
||||||
service_id = base64.b32encode(hashlib.sha1(public_key).digest()[:10]).lower().decode()
|
|
||||||
'''
|
|
||||||
elif cert:
|
elif cert:
|
||||||
# compute sha1 of public key and encode first half in base32
|
cert_ = load_certificate(FILETYPE_PEM, cert)
|
||||||
key = load_certificate(FILETYPE_ASN1, cert).get_pubkey()
|
key = cert_.get_pubkey()
|
||||||
pub_der = DerSequence()
|
public_key = key.to_cryptography_key().public_bytes_raw()
|
||||||
pub_der.decode(dump_privatekey(FILETYPE_ASN1, key))
|
service_id = get_onion(public_key)
|
||||||
public_key = RSA.construct((pub_der._seq[1], pub_der._seq[2])).exportKey('DER')[22:]
|
else:
|
||||||
service_id = base64.b32encode(hashlib.sha1(public_key).digest()[:10]).lower().decode()
|
service_id = None
|
||||||
return service_id
|
return service_id
|
||||||
|
|
||||||
def update_dict(root, data):
|
def update_dict(root, data):
|
||||||
|
|
|
@ -1028,7 +1028,7 @@ oml.updateFilterMenus = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
oml.validatePublicKey = function(value) {
|
oml.validatePublicKey = function(value) {
|
||||||
return /^[a-z0-9+\/]{16}$/.test(value);
|
return /^[a-z0-9+\/]{56}$/.test(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
oml.updateDebugMenu = function() {
|
oml.updateDebugMenu = function() {
|
||||||
|
|
Loading…
Reference in a new issue