File check-autofirma-certificate.py of Package autofirma
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import configparser
import os.path
import subprocess
import sys
from enum import Enum
debug = (len(sys.argv) > 1 and '--debug' in sys.argv)
terminal = (len(sys.argv) > 1 and '--terminal' in sys.argv)
translations = {'es': {
'Import': 'Importar',
'Cancel': 'Cancelar',
'Error': 'Error',
'Replace': 'Reemplazar',
'Error: Neither python3-PyQt5 not python3-GObject are available.':
'Error: Ni python3-PyQt5 ni python3-GObject están disponibles.',
'Cannot remove {certname} certificate':
'No se puede eliminar el certificado {certname}',
'Please delete the certificate manually from the {certmanagername}':
'Por favor, elimina el certificado manualmente del {certmanagername}',
'AutoFirma check': 'Comprobación de AutoFirma',
'Do you want to import the AutoFirma ROOT CA into {checkername}?':
'¿Quiere importar la CA AutoFirma ROOT en {checkername}?',
'The AutoFirma ROOT CA was not found in the {certmanagername}.\n\n'
'{filepath} needs to be imported for AutoFirma to work correctly.':
'No se encontró el certificado AutoFirma ROOT en el {certmanagername}'
'\n\nPara que AutoFirma funcione correctamente se necesita importar '
'el fichero\n{filepath} .',
'Do you want to replace the current AutoFirma ROOT CA certificate '
'in {checkername} with the right one?':
'¿Quiere reemplazar el certificado de CA AutoFirma ROOT actual de '
'{checkername} con el correcto?',
'The AutoFirma ROOT CA certificate found in the {certmanagername} is not '
'the correct one, probably because it was imported from a previous '
'AutoFirma version.\n\n{filepath} needs to be imported replacing the '
'current certificate for AutoFirma to work correctly.':
'El certificado de CA AutoFirma ROOT encontrado en el '
'{certmanagername} no es el correcto, probablemente porque fue '
'importado de una versión anterior.\n\nPara que AutoFirma funcione '
'correctamente se necesita importar {filepath} reemplazando '
'el certificado actual.',
'The AutoFirma ROOT CA certificate is already installed in {name}':
'El certificado de AutoFirma ROOT ya está instalado en {name}',
'Not installing the AutoFirma ROOT CA certificate in {name}':
'No se instalará el certificado de CA AutoFirma ROOT in {name}',
'Not replacing the AutoFirma ROOT CA certificate in {name}':
'No se reemplazará el certificado de CA AutoFirma ROOT in {name}',
'Firefox Certificate Manager': 'Administrador de Certificados de Firefox',
'~/.pki/nssdb directory': 'directorio ~/.pki/nssdb',
'AutoFirma_ROOT.cer file not found.':
'No se encontró el fichero AutoFirma_ROOT.cer',
'Please run the following command as root:\n':
'Por favor, ejecute el siguiente comando como root:\n',
'Firefox profile directory not found.':
'No se encontró el directorio de perfil de Firefox.'}}
def i18n(text):
import locale
locale.setlocale(locale.LC_ALL, "")
try:
lang = locale.getlocale(locale.LC_MESSAGES)[0].split('_')[0]
except Exception:
return text
try:
return translations[lang][text]
except KeyError:
return text
def get_autofirma_configurador_path():
paths = ['/usr/share/java/autofirma/',
'/usr/share/java/',
'/usr/lib64/autofirma/']
filenames = ['AutoFirmaConfigurador.jar', 'autofirmaConfigurador.jar']
for path in paths:
for filename in filenames:
filepath = os.path.join(path, filename)
if os.path.exists(filepath):
return filepath
return None
def get_autofirma_root_cer_path():
paths = ['/usr/share/java/autofirma/',
'/usr/share/java/',
'/usr/lib64/autofirma/']
for path in paths:
filepath = os.path.join(path, 'AutoFirma_ROOT.cer')
if os.path.exists(filepath):
return filepath
raise FileNotFoundError('AutoFirma_ROOT.cer file not found')
def get_file_certificate_contents(filepath):
cmd = f"openssl x509 -inform der -in '{filepath}'"
if debug:
print(cmd)
try:
return subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True)
except subprocess.CalledProcessError:
return None
def get_db_certificate_contents(profile_path, certname):
cmd = f"certutil -L -d sql:{profile_path} -n '{certname}' -a"
if debug:
print(cmd)
try:
return subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True)
except subprocess.CalledProcessError:
return None
class Qt5Ui:
@staticmethod
def is_available():
try:
from PyQt5.QtWidgets import QApplication, QMessageBox # noqa
except ModuleNotFoundError:
return False
return True
@staticmethod
def msg_dialog(title, text, informative_text, msgtype='Information'):
from PyQt5.QtWidgets import QApplication, QMessageBox
app = QApplication(sys.argv)
msgBox = QMessageBox()
msgBox.setWindowTitle(title)
msgBox.setText('<h3>' + text + '</h3>')
msgBox.setInformativeText(informative_text)
msgBox.setStandardButtons(QMessageBox.Ok)
msgBox.setDefaultButton(QMessageBox.Ok)
iconForType = {'Information': QMessageBox.Information,
'Warning': QMessageBox.Warning,
'Error': QMessageBox.Critical}
msgBox.setIcon(iconForType[msgtype])
msgBox.exec()
app.quit()
@staticmethod
def ask_user(title, text, informative_text, yes_text, no_text):
from PyQt5.QtWidgets import QApplication, QMessageBox
app = QApplication(sys.argv)
msgBox = QMessageBox()
msgBox.setWindowTitle(title)
msgBox.setText('<h3>' + text + '</h3>')
msgBox.setInformativeText(informative_text)
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
msgBox.setDefaultButton(QMessageBox.Yes)
msgBox.setIcon(QMessageBox.Question)
msgBox.button(QMessageBox.Yes).setText(yes_text)
msgBox.button(QMessageBox.No).setText(no_text)
r = msgBox.exec()
app.quit()
return r == QMessageBox.Yes
class GtkUi:
@staticmethod
def is_available():
try:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk # noqa
except ModuleNotFoundError:
return False
return True
@staticmethod
def msg_dialog(title, text, informative_text, msgtype='Information'):
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
iconForType = {'Information': Gtk.MessageType.INFO,
'Warning': Gtk.MessageType.WARNING,
'Error': Gtk.MessageType.ERROR}
dialog = Gtk.MessageDialog(None, 0, iconForType[msgtype],
Gtk.ButtonsType.OK, text)
dialog.set_title(title)
dialog.format_secondary_text(informative_text)
dialog.run()
dialog.destroy()
@staticmethod
def ask_user(title, text, informative_text, yes_text, no_text):
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
dialog = Gtk.MessageDialog(None, 0, Gtk.MessageType.QUESTION,
Gtk.ButtonsType.NONE, text)
dialog.set_title(title)
dialog.add_buttons(yes_text, Gtk.ResponseType.YES,
no_text, Gtk.ResponseType.NO)
dialog.format_secondary_text(informative_text)
response = dialog.run()
dialog.destroy()
return response == Gtk.ResponseType.YES
class TerminalUi:
@staticmethod
def is_available():
return True
@staticmethod
def msg_dialog(title, text, informative_text, msgtype='Information'):
print(title)
print(text)
print(informative_text)
@staticmethod
def ask_user(title, text, informative_text, yes_text, no_text):
print(title)
print(text)
print(informative_text)
print(f'1) {yes_text}')
print(f'2) {no_text}')
response = None
while response not in ['1', '2']:
response = input('Choose 1 or 2?')
return response == '1'
def getUi():
if terminal:
return TerminalUi
if 'GNOME' in os.getenv('XDG_CURRENT_DESKTOP'):
if GtkUi.is_available():
return GtkUi
elif Qt5Ui.is_available():
return Qt5Ui
else:
if Qt5Ui.is_available():
return Qt5Ui
elif GtkUi.is_available():
return GtkUi
print(i18n('Error: Neither python3-PyQt5 not python3-GObject are '
'available.'))
sys.exit(0)
class InstalledState(Enum):
NotInstalled = 0
InstalledOK = 1
InstalledButDifferent = 2
class CertificateChecker:
def __init__(self, certificate_path):
"""Create a CertificateChecker class."""
self.certdir = None
self.certificate_path = certificate_path
def is_autofirma_certificate_installed(self):
autofirma_cer_contents = \
get_file_certificate_contents(self.certificate_path)
if debug:
print(autofirma_cer_contents)
certname = 'AutoFirma ROOT'
certificate = get_db_certificate_contents(self.certdir, certname)
if not certificate:
certname = 'SocketAutoFirma'
certificate = get_db_certificate_contents(self.certdir, certname)
if not certificate:
return (InstalledState.NotInstalled, None)
certificate = certificate.replace(b'\r', b'')
if debug:
print(certificate)
if certificate != autofirma_cer_contents:
return (InstalledState.InstalledButDifferent, certname)
return (InstalledState.InstalledOK, certname)
def import_certificate(self):
cmd = (f"certutil -A -d sql:{self.certdir} -i {self.certificate_path} "
"-n 'AutoFirma ROOT' -t 'C,,'")
if debug:
print(cmd)
try:
subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
except subprocess.CalledProcessError:
return False
return True
def delete_certificate(self, certname):
cmd = (f"certutil -D -d sql:{self.certdir} -n '{certname}'")
if debug:
print(cmd)
try:
subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
except subprocess.CalledProcessError:
return False
return True
def replace_certificate(self, certname):
r = self.delete_certificate(certname)
if not r:
title = i18n('Error')
msg = i18n('Cannot remove {certname} certificate')
msg = msg.format(certname=certname)
msg2 = i18n('Please delete the certificate manually from the '
'{certmanagername}')
msg2 = msg2.format(certmanagername=self.certmanagername)
print(title + '\n' + msg + '\n' + msg2)
ui = getUi()
ui.msg_dialog(title, msg, msg2, 'Error')
return False
else:
# There might be more than one certificate installed
# with that name, so let's remove them all
c = 0
while self.delete_certificate(certname) and c < 10:
c += 1
return self.import_certificate()
def ask_user_if_import_certificate(self):
title = i18n('AutoFirma check')
text = i18n('Do you want to import the AutoFirma ROOT CA into '
'{checkername}?')
text = text.format(checkername=self.name)
informative_text = i18n('The AutoFirma ROOT CA was not found in the '
'{certmanagername}.\n\n{filepath} needs to '
'be imported for AutoFirma to work correctly.')
informative_text = (informative_text.format(
certmanagername=self.certmanagername,
filepath=self.certificate_path))
yes_text = i18n('Import')
no_text = i18n('Cancel')
ui = getUi()
return ui.ask_user(title, text, informative_text, yes_text, no_text)
def ask_user_if_replace_certificate(self):
title = i18n('AutoFirma check')
text = i18n('Do you want to replace the current AutoFirma ROOT CA '
'certificate in {checkername} with the right one?')
text = text.format(checkername=self.name)
msg = ('The AutoFirma ROOT CA certificate found in the '
'{certmanagername} is not the correct one, probably because it '
'was imported from a previous AutoFirma version.\n\n{filepath} '
'needs to be imported replacing the current certificate for '
'AutoFirma to work correctly.')
informative_text = i18n(msg)
informative_text = (informative_text.format(
certmanagername=self.certmanagername,
filepath=self.certificate_path))
yes_text = i18n('Replace')
no_text = i18n('Cancel')
ui = getUi()
return ui.ask_user(title, text, informative_text, yes_text, no_text)
def check(self):
installed_state, certname = self.is_autofirma_certificate_installed()
if installed_state == InstalledState.InstalledOK:
msg = i18n('The AutoFirma ROOT CA certificate is already '
'installed in {name}')
print(msg.format(name=self.name))
return True
if installed_state == InstalledState.NotInstalled:
if self.ask_user_if_import_certificate():
self.import_certificate()
else:
msg = i18n('Not installing the AutoFirma ROOT CA certificate '
'in {name}')
print(msg.format(name=self.name))
else:
if self.ask_user_if_replace_certificate():
self.replace_certificate(certname)
else:
msg = i18n('Not replacing the AutoFirma ROOT CA certificate '
'in {name}')
print(msg.format(name=self.name))
class FirefoxCertificateChecker(CertificateChecker):
def __init__(self, certificate_path):
"""Create a PkiCertificateChecker class."""
super(FirefoxCertificateChecker, self).__init__(certificate_path)
self.certdir = self.get_profile_directory('default')
self.name = 'Firefox'
self.certmanagername = i18n('Firefox Certificate Manager')
def get_profile_directory(self, profile_name):
dirname = os.path.expanduser('~/.mozilla/firefox')
config = configparser.ConfigParser(allow_no_value=True)
config.read(os.path.join(dirname, 'profiles.ini'))
is_relative = None
profile_path = None
for section in config.sections():
if section.startswith('Profile'):
if config.get(section, 'Name', fallback=None) == profile_name:
is_relative = config.get(section, 'IsRelative')
profile_path = config.get(section, 'Path')
break
else:
return None
if is_relative:
profile_path = os.path.join(dirname, profile_path)
return profile_path
class PkiCertificateChecker(CertificateChecker):
def __init__(self, certificate_path):
"""Create a PkiCertificateChecker class."""
super(PkiCertificateChecker, self).__init__(certificate_path)
self.certdir = os.path.expanduser('~/.pki/nssdb')
self.name = '~/.pki/nssdb'
self.certmanagername = i18n('~/.pki/nssdb directory')
def main():
try:
filepath = get_autofirma_root_cer_path()
except FileNotFoundError:
configurador = get_autofirma_configurador_path()
title = i18n('Error')
msg = i18n('AutoFirma_ROOT.cer file not found.')
msg2 = (i18n('Please run the following command as root:\n') +
f' java -jar {configurador} -install')
print(title + '\n' + msg + '\n' + msg2)
ui = getUi()
ui.msg_dialog(title, msg, msg2, 'Error')
sys.exit(1)
checker = FirefoxCertificateChecker(filepath)
if checker.certdir:
checker.check()
else:
msg = i18n('Firefox profile directory not found.')
checker = PkiCertificateChecker(filepath)
checker.check()
if __name__ == '__main__':
main()