Possibilité de dropper à la volée un bad guy qui a laissé expirer sa clé.

This commit is contained in:
Vincent Le Gallic 2013-07-29 23:07:19 +02:00
parent 8e3a76919c
commit b522b4a741

100
client.py
View file

@ -52,7 +52,7 @@ NEWROLES = None
SERVER = None SERVER = None
## GPG Definitions ## GPG Definitions
#: Path to gpg binary #: Path du binaire gpg
GPG = '/usr/bin/gpg' GPG = '/usr/bin/gpg'
#: Paramètres à fournir à gpg en fonction de l'action désirée #: Paramètres à fournir à gpg en fonction de l'action désirée
@ -80,12 +80,12 @@ GPG_TRUSTLEVELS = {
def gpg(command, args=None, verbose=False): def gpg(command, args=None, verbose=False):
"""Lance gpg pour la commande donnée avec les arguments """Lance gpg pour la commande donnée avec les arguments
donnés. Renvoie son entrée standard et sa sortie standard.""" donnés. Renvoie son entrée standard et sa sortie standard."""
full_command = [GPG] full_command = [GPG]
full_command.extend(GPG_ARGS[command]) full_command.extend(GPG_ARGS[command])
if args: if args:
full_command.extend(args) full_command.extend(args)
if verbose: if verbose or VERB:
stderr = sys.stderr stderr = sys.stderr
else: else:
stderr = subprocess.PIPE stderr = subprocess.PIPE
@ -95,7 +95,7 @@ def gpg(command, args=None, verbose=False):
stdout = subprocess.PIPE, stdout = subprocess.PIPE,
stderr = stderr, stderr = stderr,
close_fds = True) close_fds = True)
if not verbose: if not (verbose or VERB):
proc.stderr.close() proc.stderr.close()
return proc.stdin, proc.stdout return proc.stdin, proc.stdout
@ -315,41 +315,72 @@ def update_keys():
_, stdout = gpg("receive-keys", [key for _, key in keys.values() if key]) _, stdout = gpg("receive-keys", [key for _, key in keys.values() if key])
return stdout.read().decode("utf-8") return stdout.read().decode("utf-8")
def check_keys(): def check_keys(recipients=None, interactive=False, drop_invalid=False):
"""Vérifie les clés existantes""" """Vérifie les clés, c'est-à-dire, si le mail est présent dans les identités du fingerprint,
if VERB: et que la clé est de confiance (et non expirée/révoquée).
print("M : l'uid correspond au mail du fingerprint\nC : confiance OK (inclu la vérification de non expiration).\n")
* Si ``recipients`` est fourni, vérifie seulement ces recipients.
Renvoie la liste de ceux qu'on n'a pas droppés.
* Si ``interactive=True``, demandera confirmation pour dropper un recipient dont la clé est invalide.
* Sinon, et si ``drop_invalid=True``, droppe les recipients automatiquement.
* Si rien n'est fourni, vérifie toutes les clés et renvoie juste un booléen disant si tout va bien.
"""
if QUIET:
interactive = False
trusted_recipients = []
keys = all_keys() keys = all_keys()
if recipients is None:
SPEAK = VERB
else:
SPEAK = False
keys = {u : val for (u, val) in keys.iteritems() if u in recipients}
if SPEAK:
print("M : le mail correspond à un uid du fingerprint\nC : confiance OK (inclut la vérification de non expiration).\n")
_, gpgout = gpg('list-keys') _, gpgout = gpg('list-keys')
localring = parse_keys(gpgout) localring = parse_keys(gpgout)
failed = False for (recipient, (mail, fpr)) in keys.iteritems():
for (mail, fpr) in keys.values(): failed = u""
if fpr: if not fpr is None:
if VERB: if SPEAK:
print((u"Checking %s" % (mail)).encode("utf-8"), end="") print((u"Checking %s" % (mail)).encode("utf-8"), end="")
key = localring.get(fpr, None) key = localring.get(fpr, None)
# On vérifie qu'on possède la clé… # On vérifie qu'on possède la clé…
if not key is None: if not key is None:
# …qu'elle correspond au mail… # …qu'elle correspond au mail…
if any([u"<%s>" % (mail,) in u["uid"] for u in key["uids"]]): if any([u"<%s>" % (mail,) in u["uid"] for u in key["uids"]]):
if VERB: if SPEAK:
print("M ", end="") print("M ", end="")
meaning, trustvalue = GPG_TRUSTLEVELS[key["trustletter"]] meaning, trustvalue = GPG_TRUSTLEVELS[key["trustletter"]]
# … et qu'on lui fait confiance # … et qu'on lui fait confiance
if not trustvalue: if not trustvalue:
print((u"--> Fail on %s:%s\nLa confiance en la clé est : %s" % (fpr, mail, meaning,)).encode("utf-8")) failed = u"La confiance en la clé est : %s" % (meaning,)
failed = True elif SPEAK:
elif VERB:
print("C ", end="") print("C ", end="")
else: else:
print((u"--> Fail on %s:%s\n!! Le fingerprint et le mail ne correspondent pas !" % (fpr, mail)).encode("utf-8")) failed = u"!! Le fingerprint et le mail ne correspondent pas !"
failed = True
else: else:
print((u"--> Fail on %s:%s\nPas (ou trop) de clé avec ce fingerprint." % (fpr, mail)).encode("utf-8")) failed = u"Pas (ou trop) de clé avec ce fingerprint."
failed = True if SPEAK:
if VERB:
print("") print("")
return not failed if failed:
if not QUIET:
print((u"--> Fail on %s:%s\n--> %s" % (mail, fpr, failed)).encode("utf-8"))
if not recipients is None:
# On cherche à savoir si on droppe ce recipient
drop = True # par défaut, on le drope
if interactive:
if not confirm(u"Abandonner le chiffrement pour cette clé ? (Si vous la conservez, il est posible que gpg crashe)"):
drop = False # sauf si on a répondu non à "abandonner ?"
elif not drop_invalid:
drop = False # ou bien si drop_invalid ne nous autorise pas à le dropper silencieusement
if not drop:
trusted_recipients.append(recipient)
else:
trusted_recipients.append(recipient)
if recipients is None:
return set(keys.keys()).issubset(trusted_recipients)
else:
return trusted_recipients
def get_recipients_of_roles(roles): def get_recipients_of_roles(roles):
"""Renvoie les destinataires d'un rôle""" """Renvoie les destinataires d'un rôle"""
@ -366,11 +397,12 @@ def get_dest_of_roles(roles):
return [u"%s : %s (%s)" % (rec, allkeys[rec][0], allkeys[rec][1]) return [u"%s : %s (%s)" % (rec, allkeys[rec][0], allkeys[rec][1])
for rec in get_recipients_of_roles(roles) if allkeys[rec][1]] for rec in get_recipients_of_roles(roles) if allkeys[rec][1]]
def encrypt(roles, contents): def encrypt(roles, contents, interactive_trust=True, drop_invalid=False):
"""Chiffre ``contents`` pour les ``roles`` donnés""" """Chiffre le contenu pour les roles donnés"""
allkeys = all_keys() allkeys = all_keys()
recipients = get_recipients_of_roles(roles) recipients = get_recipients_of_roles(roles)
recipients = check_keys(recipients, interactive=interactive_trust, drop_invalid=drop_invalid)
fpr_recipients = [] fpr_recipients = []
for recipient in recipients: for recipient in recipients:
fpr = allkeys[recipient][1] fpr = allkeys[recipient][1]
@ -394,10 +426,10 @@ def decrypt(contents):
stdin.close() stdin.close()
return stdout.read().decode("utf-8") return stdout.read().decode("utf-8")
def put_password(name, roles, contents): def put_password(name, roles, contents, interactive_trust=True, drop_invalid=False):
"""Dépose le mot de passe après l'avoir chiffré pour les """Dépose le mot de passe après l'avoir chiffré pour les
destinataires donnés""" destinataires donnés"""
success, enc_pwd_or_error = encrypt(roles, contents) success, enc_pwd_or_error = encrypt(roles, contents, interactive_trust, drop_invalid)
if NEWROLES != None: if NEWROLES != None:
roles = NEWROLES roles = NEWROLES
if VERB: if VERB:
@ -521,7 +553,7 @@ def show_file(fname):
os.waitpid(proc.pid, 0) os.waitpid(proc.pid, 0)
def edit_file(fname): def edit_file(fname, interactive_trust=True, drop_invalid=False):
"""Modifie/Crée un fichier""" """Modifie/Crée un fichier"""
gotit, value = get_files([fname])[0] gotit, value = get_files([fname])[0]
nfile = False nfile = False
@ -563,18 +595,18 @@ C'est-à-dire pour les utilisateurs suivants :\n%s""" % (
ntexte = editor(texte, annotations) ntexte = editor(texte, annotations)
if ((not nfile and ntexte in [u'', texte] and NEWROLES == None) or # Fichier existant vidé ou inchangé if ((not nfile and ntexte in [u'', texte] and NEWROLES == None) or # Fichier existant vidé ou inchangé
(nfile and ntexte == u'')): # Nouveau fichier créé vide (nfile and ntexte == u'')): # Nouveau fichier créé vide
print(u"Pas de modification effectuée".encode("utf-8")) print(u"Pas de modification effectuée".encode("utf-8"))
else: else:
ntexte = texte if ntexte == None else ntexte ntexte = texte if ntexte == None else ntexte
success, message = put_password(fname, value['roles'], ntexte) success, message = put_password(fname, value['roles'], ntexte, interactive_trust, drop_invalid)
print(message.encode("utf-8")) print(message.encode("utf-8"))
def confirm(text): def confirm(text):
"""Demande confirmation, sauf si on est mode ``FORCED``""" """Demande confirmation, sauf si on est mode ``FORCED``"""
if FORCED: return True if FORCED: return True
while True: while True:
out = raw_input((text + u' (O/N)').encode("utf-8")).lower() out = raw_input((text + u' (o/n)').encode("utf-8")).lower()
if out == 'o': if out == 'o':
return True return True
elif out == 'n': elif out == 'n':
@ -597,7 +629,7 @@ def my_update_keys():
"""Met à jour les clés existantes et affiche le résultat""" """Met à jour les clés existantes et affiche le résultat"""
print(update_keys().encode("utf-8")) print(update_keys().encode("utf-8"))
def recrypt_files(): def recrypt_files(interactive_trust=False, drop_invalid=True):
"""Rechiffre les fichiers""" """Rechiffre les fichiers"""
# Ici, la signification de NEWROLES est : on ne veut rechiffrer que les fichiers qui ont au moins un de ces roles # Ici, la signification de NEWROLES est : on ne veut rechiffrer que les fichiers qui ont au moins un de ces roles
rechiffre_roles = NEWROLES rechiffre_roles = NEWROLES
@ -708,7 +740,9 @@ if __name__ == "__main__":
help="Lister les serveurs") help="Lister les serveurs")
action_grp.add_argument('--recrypt-files', action='store_const', dest='action', action_grp.add_argument('--recrypt-files', action='store_const', dest='action',
default=show_file, const=recrypt_files, default=show_file, const=recrypt_files,
help="Rechiffrer les mots de passe. (Avec les mêmes rôles qu'avant, sert à rajouter un lecteur)") help="""Rechiffrer les mots de passe.
(Avec les mêmes rôles que ceux qu'ils avant.
Cela sert à mettre à jour les recipients pour qui un password est chiffré)""")
parser.add_argument('--roles', nargs='?', default=None, parser.add_argument('--roles', nargs='?', default=None,
help="""Liste de roles (séparés par des virgules). help="""Liste de roles (séparés par des virgules).