Danc cette 1°partie on va essayer de comprendre comment se déroule l'authentification sous Twisted et on va mettre en place notre serveur SSH.
Je coupe le tuto en 2 parties :
Partie 1 : comprendre l'authentification, mise en place serveur SSH
Partie 2 : mise en place du shell .
Étape n°1 : créer le serveur ssh ( putain tu m'étonnes John) .
C'est assez simple à faire si on comprend le fonctionnement de l'authentification avec Twisted, et ca c'est pas gagné...
Enfin heureusement que je suis là pour tout vous expliquer... ou du moins ce que j'en ai compris.
Vous êtes prêts ? Ça va être long et chiant ...
Avec twisted l'authentification est géré via le module twisted.cred .
Voici comment elle se déroule :
1)l'utilisateur détient un couple utilisateur/pass : les crédentials
NB: sauf s'il est con
2)l'utilisateur va fournir ces crédentials au portal ( ainsi qu'une requête d'interface qui définit les actions qu'aura droit l'utilisateur )
NB : si l'utilisateur souhaite se connecter à son compte d'un site web : l'interface va définir ses droits, par exemple : suppression du compte , edition du compte, etc..
3)le portalva envoyer les credentials à notre credential checker (une bdd,un fichier, le sytème, etc) qui va vérifier le couple
NB : pour notre utilisateur qui souhaite se connecter, l'autthentification se fera via bdd
4)si la vérification est bonne, le credencial checker va renvoyer au portal un ID lié au credential (l'avatarID) .
NB: l'avatarID correspondra à l'id du compte de l'utilisateur
5)leportalva devoir faire de l'avatarID un objet, pour ce faire il l'envoie au realm
NB : bah oui sinon on va pouvoir faire grand chose
6)le realm va renvoyer au portal (qui va renvoyer à l'utilisateur), l' objet (implémentant l'interface), permettant à l'utilisateur de faire mumuse (l'avatar) .
NB: on prend l'id du compte, on prend l'interface, on les mélange, et on a le droit à notre bel objet (une interface ne peut s'instancier ) définissant les actions auquelles aura accès notre utilisateur
Voilà, compris ? si vous comprenez toujours rien, relisez et relisez, ca rentrera bien un jour... enfin j'espère...
Bon, on va commencer par créer notre factory :
#-*- coding:Utf-8 -*-
from twisted.conch.ssh import factory, keys
from twisted.cred import portal
from twisted.conch import checkersfrom twisted.python.randbytes import secureRandom
from Crypto.PublicKey import DSA
from twisted.internet import reactor
import os
if __name__ == '__main__':
monFactory = factory.SSHFactory()
Un factory est lié à un protocole : ici on se sert de factory.SSHFactory , ce qui signifie que la classe est liée au protocole ssh ( putain, tu m'étonnes John ) .
Le factory va instancier le protocole auquel il est lié et gérer sa configuration :
notre SSHFactory va par exemple gérer les clés privée/clé publique .
Maintenant il nous faut créer notre portal :
pour ce faire, on rajoute à la suite :
monFactory.portal= portal.Portal( monRealm() )
C'est notre factory qui va contenir notre portal, pourquoi ? pour qu'ils puissent se raconter des choses coquines ou s'échanger des infos( notre protocole pourra lui envoyer les credentials ,etc... )
monPortalprend en paramètre monRealm : afin de s'échanger également des informations ( portal lui envoie l'avatarID,etc...) .
À noter qu'il faut impérativement appeler notre attribut portal
Maintenant il faut pouvoir envoyer nos credentials à notre credential checker ; on rajoute donc ceci :
monFactory.portal.registerChecker( checkers.UNIXPasswordDatabase() ) )
Il faut télécharger le module shadow ici .
On définit comme credential checker de portal : UNIXPasswordDatabase .
Cette classe va chiffrer le mot de passe de l'utilisateur et le vérifier avec celui de /etc/shadow . S'ils correspondent, on pourra alors se logguer sur le serveur .
Mais j'ai eu un problème avec le module checkers : /usr/[local/]lib/python2.X/site-packages/twisted/conch/checkers.py, et plus particulièrement avec la fonction verifyCryptedPassword(), j'ai donc remplacé la ligne :
salt = '$1$' + crypted.split('$')[2]
par :
salt = crypted[:3] + crypted.split('$')[2]
Et maintenant UNIXPasswordDatabase fonctionne à merveille.
Notre serveur ssh est bientôt terminé, mais il faut impérativement implanter 2 attributs de monFactory:
publicKeys et privateKeys.(cf le rôle d'un factory un peu plus haut ).
Pour ce faire, on va créer une fonction cles(), qui va générer nos clés privée/publique.
On commence par créer la clé(privée)DSA de 4096 bits via :
cle = DSA.generate( 4096, secureRandom)
secureRandom permet de générer un nombre aléatoire d'octets ; soit ici 512 octets (4096 bits) .
Ensuite, il faut tester si les clés privée/publique existent, et si elles ne le sont pas, il faut les créer .
Voici donc que donne la fonction cles() :
def cles():
maCle = DSA.generate( 4096, secureRandom)#on génère une clé
if not os.path.exists('public.key') :
fichier = open('public.key','w')
fichier.write(keys.Key(maCle).public().toString('openssh') )
fichier.close()
if not os.path.exists('private.key') :
fichier = open('private.key','w')
fichier.write(keys.Key(maCle).toString('openssh') )
fichier.close()
Si le fichier public.key ou private.key n'existe pas alors on l'ouvre, on écrit dedans la chaine représentant la clé liée et on ferme.
Pour le faire, on instancie la classe Key qui prend en paramètre maClé .
Si on veut créer la clé publique, alors on utilise public() afin de la générer depuis maClé( qui est la clé privée) .
Enfin, on utilise toString() pour faire de nos clés des chaines ; openssh permet d'identifier nos chaines comme des clés pour Openssh.
Maintenant que nos clés sont prêtes, on peut implanter nos 2 attributs publicKeys et privateKeys. qui doivent être des dictionnaires ayant comme clé : le type de l'algorithme de la clé publique/privée et comme valeur la clé publique/privée.
On rajoute donc dans notre __main__ ceci :
cles()# bah oui faut bien appeler notre si belle fonction
monFactory.publicKeys = {'ssh-dss': keys.Key.fromFile('public.key') }
monFactory.privateKeys = {'ssh-dss': keys.Key.fromFile('private.key') }
Donc la clé de nos dictionnaires sera :
ssh-dss : parce qu'on a utilisé l'algo DSA ; si on avait utilisé RSA, alors la cé serait ssh-rsa .
Ensuite pour récupérer nos clés publique/privée, on a plus qu'à utiliser la méthode fromFile() de Key, qui va nous les retourner .
Et voilà, il nous reste plus qu'à lancer notre reactor:
reactor.listenTCP(2222, monFactory)
reactor.run()
Pour rappel, le reactor va catcher les évènements se déroulant sur le port 2222, et lancer les méthode liées.
Voici le code complet de notre chef d'oeuvre :
def cles():
cle = DSA.generate(4096, secureRandom)#on génère une clé
if not os.path.exists('public.key') :
fichier = open('public.key','w')
fichier.write(keys.Key(cle).public().toString('openssh') )
fichier.close()
if not os.path.exists('private.key') :
fichier = open('private.key','w')
fichier.write(keys.Key(cle).toString('openssh') )
fichier.close()
if __name__ == '__main__':
monFactory = factory.SSHFactory()
monFactory.portal = portal.Portal( MonRealm() )
monFactory.portal.registerChecker( checkers.UNIXPasswordDatabase() )
cles()
monFactory.publicKeys = {'ssh-dss': keys.Key.fromFile('public.key') }
monFactory.privateKeys = {'ssh-dss': keys.Key.fromFile('private.key') }
reactor.listenTCP(2222, monFactory)
reactor.run()
La première partie est terminée, bientôt la 2° ... enfin je vais essayer de me dépêcher .
On mettra en place un shell pour l'utilisateur qui se connecte à notre serveur .