Skip to content

Commit

Permalink
Added generation of analyst key pair
Browse files Browse the repository at this point in the history
  • Loading branch information
Alessio Franceschini committed Sep 25, 2024
1 parent eb01063 commit db694bd
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 19 deletions.
8 changes: 4 additions & 4 deletions backend/globaleaks/db/migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from globaleaks import __version__, models, \
DATABASE_VERSION, FIRST_DATABASE_VERSION_SUPPORTED, LANGUAGES_SUPPORTED_CODES
from globaleaks.db.appdata import load_appdata, db_load_defaults
from globaleaks.db.migrations.update_69 import User_v_68, Tenant_v_68, Subscriber_v_68, InternalFile_v_68, \
from globaleaks.db.migrations.update_69 import Field_v_68, InternalTipAnswers_v_68, User_v_68, Tenant_v_68, Subscriber_v_68, InternalFile_v_68, \
ReceiverFile_v_68
from globaleaks.orm import db_log

Expand Down Expand Up @@ -57,7 +57,7 @@
('Context', [Context_v_61, 0, 0, 0, 0, 0, 0, 0, 0, 0, Context_v_63, 0, models._Context, 0, 0, 0, 0, 0]),
('CustomTexts', [models._CustomTexts, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('EnabledLanguage', [models._EnabledLanguage, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('Field', [models._Field, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('Field', [Field_v_68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, models._Field]),
('FieldAttr', [FieldAttr_v_52, models._FieldAttr, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('FieldOption', [models._FieldOption, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('FieldOptionTriggerField', [models._FieldOptionTriggerField, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
Expand All @@ -67,7 +67,7 @@
('IdentityAccessRequestCustodian', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, models._IdentityAccessRequestCustodian, 0, 0, 0, 0]),
('InternalFile', [InternalFile_v_64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, InternalFile_v_68, 0, 0, 0, models._InternalFile]),
('InternalTip', [InternalTip_v_52, InternalTip_v_57, 0, 0, 0, 0, InternalTip_v_59, 0, InternalTip_v_63, 0, 0, 0, InternalTip_v_64, InternalTip_v_66, 0, models._InternalTip, 0, 0]),
('InternalTipAnswers', [models._InternalTipAnswers, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('InternalTipAnswers', [InternalTipAnswers_v_68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, models._InternalTipAnswers]),
('InternalTipData', [models._InternalTipData, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('Mail', [models._Mail, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('Message', [Message_v_64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1]),
Expand All @@ -82,7 +82,7 @@
('SubmissionStatusChange', [SubmissionStatusChange_v_54, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]),
('Step', [models._Step, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
('Subscriber', [Subscriber_v_52, Subscriber_v_62, 0, 0, 0, 0, 0, 0, 0, 0, 0, Subscriber_v_67, 0, 0, 0, 0, Subscriber_v_68, models._Subscriber]),
('Tenant', [Tenant_v_52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Tenant_v_68, models._Tenant]),
('Tenant', [Tenant_v_52, Tenant_v_68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, models._Tenant]),
('User', [User_v_52, User_v_54, 0, User_v_56, 0, User_v_61, 0, 0, 0, 0, User_v_64, 0, 0, User_v_66, 0, User_v_68, 0, models._User]),
('WhistleblowerFile', [WhistleblowerFile_v_57, 0, 0, 0, 0, 0, WhistleblowerFile_v_64, 0, 0, 0, 0, 0, 0, WhistleblowerFile_v_66, 0, models._WhistleblowerFile, 0, 0]),
('InternalTipForwarding', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, models._InternalTipForwarding]),
Expand Down
100 changes: 86 additions & 14 deletions backend/globaleaks/db/migrations/update_69/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from globaleaks import models
from globaleaks.models.enums import EnumFieldInstance
from globaleaks.utils.crypto import GCE, Base64Encoder
from globaleaks.db.migrations.update import MigrationBase
from globaleaks.models import Model, EnumSubscriberStatus, EnumStateFile, EnumVisibility, EnumUserRole, EnumUserStatus
from globaleaks.models.properties import *
from globaleaks.utils.utility import datetime_now, datetime_null
from globaleaks.utils.utility import datetime_never, datetime_now, datetime_null
from globaleaks.utils.log import log


class Subscriber_v_68(Model):
Expand Down Expand Up @@ -34,6 +38,30 @@ class Tenant_v_68(Model):
active = Column(Boolean, default=False, nullable=False)


class Field_v_68(Model):
__tablename__ = 'field'

id = Column(UnicodeText(36), primary_key=True, default=uuid4)
tid = Column(Integer, default=1, nullable=False)
x = Column(Integer, default=0, nullable=False)
y = Column(Integer, default=0, nullable=False)
width = Column(Integer, default=0, nullable=False)
label = Column(JSON, default=dict, nullable=False)
description = Column(JSON, default=dict, nullable=False)
hint = Column(JSON, default=dict, nullable=False)
placeholder = Column(JSON, default=dict, nullable=False)
required = Column(Boolean, default=False, nullable=False)
multi_entry = Column(Boolean, default=False, nullable=False)
triggered_by_score = Column(Integer, default=0, nullable=False)
step_id = Column(UnicodeText(36), index=True)
fieldgroup_id = Column(UnicodeText(36), index=True)
type = Column(UnicodeText, default='inputbox', nullable=False)
instance = Column(Enum(EnumFieldInstance),
default='instance', nullable=False)
template_id = Column(UnicodeText(36), index=True)
template_override_id = Column(UnicodeText(36), index=True)


class InternalFile_v_68(Model):
"""
This model keeps track of submission files
Expand All @@ -51,6 +79,18 @@ class InternalFile_v_68(Model):
reference_id = Column(UnicodeText(36), default='', nullable=False)


class InternalTipAnswers_v_68(Model):
"""
This is the internal representation of Tip Questionnaire Answers
"""
__tablename__ = 'internaltipanswers'

internaltip_id = Column(UnicodeText(36), primary_key=True)
questionnaire_hash = Column(UnicodeText(64), primary_key=True)
creation_date = Column(DateTime, default=datetime_now, nullable=False)
answers = Column(JSON, default=dict, nullable=False)


class ReceiverFile_v_68(Model):
"""
This models stores metadata of files uploaded by recipients intended to bes
Expand Down Expand Up @@ -93,23 +133,28 @@ class User_v_68(Model):
mail_address = Column(UnicodeText, default='', nullable=False)
language = Column(UnicodeText(12), nullable=False)
password_change_needed = Column(Boolean, default=True, nullable=False)
password_change_date = Column(DateTime, default=datetime_null, nullable=False)
password_change_date = Column(
DateTime, default=datetime_null, nullable=False)
crypto_prv_key = Column(UnicodeText(84), default='', nullable=False)
crypto_pub_key = Column(UnicodeText(56), default='', nullable=False)
crypto_rec_key = Column(UnicodeText(80), default='', nullable=False)
crypto_bkp_key = Column(UnicodeText(84), default='', nullable=False)
crypto_escrow_prv_key = Column(UnicodeText(84), default='', nullable=False)
crypto_escrow_bkp1_key = Column(UnicodeText(84), default='', nullable=False)
crypto_escrow_bkp2_key = Column(UnicodeText(84), default='', nullable=False)
crypto_escrow_bkp1_key = Column(
UnicodeText(84), default='', nullable=False)
crypto_escrow_bkp2_key = Column(
UnicodeText(84), default='', nullable=False)
change_email_address = Column(UnicodeText, default='', nullable=False)
change_email_token = Column(UnicodeText, unique=True)
change_email_date = Column(DateTime, default=datetime_null, nullable=False)
notification = Column(Boolean, default=True, nullable=False)
forcefully_selected = Column(Boolean, default=False, nullable=False)
can_delete_submission = Column(Boolean, default=False, nullable=False)
can_postpone_expiration = Column(Boolean, default=True, nullable=False)
can_grant_access_to_reports = Column(Boolean, default=False, nullable=False)
can_transfer_access_to_reports = Column(Boolean, default=False, nullable=False)
can_grant_access_to_reports = Column(
Boolean, default=False, nullable=False)
can_transfer_access_to_reports = Column(
Boolean, default=False, nullable=False)
can_redact_information = Column(Boolean, default=False, nullable=False)
can_mask_information = Column(Boolean, default=True, nullable=False)
can_reopen_reports = Column(Boolean, default=True, nullable=False)
Expand All @@ -121,19 +166,46 @@ class User_v_68(Model):
# BEGIN of PGP key fields
pgp_key_fingerprint = Column(UnicodeText, default='', nullable=False)
pgp_key_public = Column(UnicodeText, default='', nullable=False)
pgp_key_expiration = Column(DateTime, default=datetime_null, nullable=False)
pgp_key_expiration = Column(
DateTime, default=datetime_null, nullable=False)
# END of PGP key fields

accepted_privacy_policy = Column(DateTime, default=datetime_null, nullable=False)
accepted_privacy_policy = Column(
DateTime, default=datetime_null, nullable=False)
clicked_recovery_key = Column(Boolean, default=False, nullable=False)


class MigrationScript(MigrationBase):

def epilogue(self):
new_configuration = self.model_to['Config']()
new_configuration.var_name = 'url_file_analysis'
new_configuration.value = 'http://localhost/api/v1/scan'
self.session_new.add(new_configuration)
def add_global_stat_prv_key_to_users(self, global_stat_prv_key):
users = self.session_new.query(self.model_from['User']) \
.filter(self.model_from['User'].tid == 1)\
.filter(self.model_from['User'].role.in_([EnumUserRole.admin.name, EnumUserRole.analyst.name]))

for user in users:
crypto_stat_key = Base64Encoder.encode(
GCE.asymmetric_encrypt(user.crypto_pub_key, global_stat_prv_key)).decode()
self.session_new.query(models.User) \
.filter(models.User.id == user.id)\
.update({'crypto_global_stat_prv_key': crypto_stat_key})

def add_global_stat_keys(self):
global_stat_prv_key, global_stat_pub_key = GCE.generate_keypair()
global_stat_pub_key_config = self.model_to['Config']()
global_stat_pub_key_config.var_name = 'global_stat_pub_key'
global_stat_pub_key_config.value = global_stat_pub_key
self.session_new.add(global_stat_pub_key_config)
self.entries_count['Config'] += 1
self.add_global_stat_prv_key_to_users(global_stat_prv_key)

def add_file_analisys_url(self):
file_analisys_config = self.model_to['Config']()
file_analisys_config.var_name = 'url_file_analysis'
file_analisys_config.value = 'http://localhost/api/v1/scan'
self.session_new.add(file_analisys_config)
log.info("FILE ANALISYS %s" % file_analisys_config.value)
self.entries_count['Config'] += 1

self.entries_count['Config'] += 1
def epilogue(self):
self.add_file_analisys_url()
self.add_global_stat_keys()
13 changes: 13 additions & 0 deletions backend/globaleaks/handlers/wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@
from globaleaks.utils.log import log
from globaleaks.utils.sock import isIPAddress

def generate_analyst_key_pair(session, admin_user):
global_stat_prv_key, global_stat_pub_key = GCE.generate_keypair()
global_stat_pub_key_config = session.query(models.Config) \
.filter(models.Config.tid == 1, models.Config.var_name == 'global_stat_pub_key')
global_stat_pub_key_config.value = global_stat_pub_key

crypto_stat_key = Base64Encoder.encode(
GCE.asymmetric_encrypt(admin_user.crypto_pub_key, global_stat_prv_key)).decode()
session.query(models.User) \
.filter(models.User.id == admin_user.id)\
.update({'crypto_global_stat_prv_key': crypto_stat_key})

def db_wizard(session, tid, hostname, request):
"""
Expand Down Expand Up @@ -73,6 +84,8 @@ def db_wizard(session, tid, hostname, request):
admin_user = db_create_user(session, tid, None, admin_desc, language)
db_set_user_password(session, tid, admin_user, request['admin_password'])
admin_user.password_change_needed = (tid != 1)
if tid == 1:
generate_analyst_key_pair(session, admin_user)

if encryption and escrow:
node.set_val('crypto_escrow_pub_key', crypto_escrow_pub_key)
Expand Down
2 changes: 2 additions & 0 deletions backend/globaleaks/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ class _Field(Model):
instance = Column(Enum(EnumFieldInstance), default='instance', nullable=False)
template_id = Column(UnicodeText(36), index=True)
template_override_id = Column(UnicodeText(36), index=True)
statistical = Column(Boolean, default=False, nullable=False)

@declared_attr
def __table_args__(self):
Expand Down Expand Up @@ -1068,6 +1069,7 @@ class _User(Model):
crypto_escrow_prv_key = Column(UnicodeText(84), default='', nullable=False)
crypto_escrow_bkp1_key = Column(UnicodeText(84), default='', nullable=False)
crypto_escrow_bkp2_key = Column(UnicodeText(84), default='', nullable=False)
crypto_global_stat_prv_key = Column(UnicodeText(84), default='', nullable=True)
change_email_address = Column(UnicodeText, default='', nullable=False)
change_email_token = Column(UnicodeText, unique=True)
change_email_date = Column(DateTime, default=datetime_null, nullable=False)
Expand Down
1 change: 1 addition & 0 deletions backend/globaleaks/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ def initialize_config(session, tid, mode):
variables[name] = root_tenant_node[name]

variables['url_file_analysis'] = 'http://localhost/api/v1/scan'
variables['global_stat_pub_key'] = ''

for name, value in variables.items():
session.add(Config({'tid': tid, 'var_name': name, 'value': value}))
Expand Down
3 changes: 2 additions & 1 deletion backend/globaleaks/models/config_desc.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ class Bool(Item):
'version_db': Int(default=DATABASE_VERSION),
'wizard_done': Bool(default=False),
'uuid': Unicode(default=uuid4),
'url_file_analysis': Unicode(default='http://localhost/api/v1/scan')
'url_file_analysis': Unicode(default='http://localhost/api/v1/scan'),
'global_stat_pub_key': Unicode(default='')
}


Expand Down

0 comments on commit db694bd

Please sign in to comment.