Désactiver les Router Advertisement sur les modem Thomson

Toute personne ayant un abonnement ADSL chez OVH est un geek et sait donc que l’interface web des modems Thomson est… disons très perfectible pour rester gentil.

Je vais donc détailler ci-dessous la méthode permettant de désactiver réellement les Router Advertisement envoyés par le modem, ce qui permettra d’avoir son propre routeur IPv6 (et sans la nécessité d’utiliser le modem en bridge).

Disclaimer

Comme d’habitude, la procédure risque de vous déconnecter d’Internet et tant pis pour vous si vous cassez tout. Vous devez avoir une bonne connaissance du protocole IPv6, notamment au niveau des échanges effectués en ICMPv6, du routage et du protocole de découverte des voisins (NDP aka Neigbour Discovery Protocol).

Pensez à sauvegarder la configuration de votre modem afin de toujours pouvoir revenir en arrière dans une configuration fonctionnelle.
J’ai galéré pendant 2 jours avant d’avoir un truc fonctionnel, il y a donc de forte chance pour que ça ne fonctionne pas du premier coup.

Juste pour rire, la méthode qui ne fonctionne pas.

  • Allez sur la page d’administration du modem
  • Cliquez sur Home Network > Interfaces > Configure
  • Décochez la case « Use Stateless DHCPv6 Server » et valider en appuyant sur Apply
  • Et voilà, ça marche pas (même après un redémarrage du modem) !

technicolor_radvd

Par « ça marche pas », j’entends par là que le modem continue toujours à balancer ses RA et ses DNS, comme on peut le vérifier en écoutant le réseau ou via les logs de mon propre démon radvd :

Aug 15 14:18:53 tefnout radvd[5802]: our AdvOtherConfigFlag on enp2s0 doesn't agree with fe80::xxxx:xxxx:xxxx:xxxx
Aug 15 14:18:53 tefnout radvd[5802]: RDNSS address 2001:41d0:1:e2b8::1 received on enp2s0 from fe80::xxxx:xxxx:xxxx:xxxx is not advertised by us
Aug 15 14:18:53 tefnout radvd[5802]: RDNSS address 2001:41d0:3:163::1 received on enp2s0 from fe80::xxxx:xxxx:xxxx:xxxx is not advertised by us
Aug 15 14:18:53 tefnout radvd[5802]: our AdvValidLifetime on enp2s0 for 2001:41d0:fe1c:1300:: doesn't agree with fe80::xxxx:xxxx:xxxx:xxxx
Aug 15 14:18:53 tefnout radvd[5802]: our AdvPreferredLifetime on enp2s0 for 2001:41d0:fe1c:1300:: doesn't agree with fe80::xxxx:xxxx:xxxx:xxxx

Et maintenant la méthode qui fonctionne

  • Ouvrez putty et connectez-vous en précisant l’ip du modem ainsi que le protocol Telnet
  • Connectez-vous. Les identifiants par défaut sont Administrator pour le nom d’utilisateur et il n’y a pas de mot de passe.
  • Tapez ce qui suit, en adaptant bien sur avec votre prefix/subnet :
# ajout d'une délégation
:ip rt6advd pdadd intf=LocalNetwork subnet-id=2001:41d0:fe1c:xxxx::/64 interface-id=slaac origin=Internet types=+dhcp+6rd+6to4+ula aflag=enabled lflag=enabled
# suppression des router advertisements
:ip rt6advd pfxdelete subnet-id=2001:41d0:fe1c:xxxx::/64

# ajout d'une route, pour que le modem balance les paquets en retour à votre router (nécessaire en mode router)
:ip rtadd dst=2001:41d0:fe1c:xxxx::/64 gateway=fe80::20c:29ff:xxxx:fbf intf=LocalNetwork status=up metric=0 static=enabled
# suppression de l'ancienne route
:ip rtdelete dst=2001:41d0:fe1c:xxxx::/64 intf=LocalNetwork
  • Et on termine bien sur par un saveall  pour que la configuration soit persistante (survive à un reboot)

Les commandes qui commencent par :ip rt6advd  ne servent pas qu’à envoyer des Router Advertisements, elles servent également à autoriser la délégation du préfixe dans le réseau local (= avoir un autre routeur IPv6 en plus du thomson). Il n’est donc pas possible de simplement détacher l’interface avec la commande :ip rt6advd ifdetach intf=LocalNetwork , cela aura pour effet de couper toute connectivité IPv6.

Divers

Pour aligner le modem avec les valeurs par défaut de radvd :

:ip rt6advd pfxadd intf=LocalNetwork subnet-id={VOTRE-PREFIX-IPV6}::/64 interface-id=slaac vltime=86400 pltime=14400 aflag=disabled lflag=enabled

On peut lister les interfaces sur lesquels il balance du RA avec la commande suivante : :ip rt6advd iflist

De manière générale, la commande help  aide dans la plupart des cas à découvrir toutes les fonctionnalités du modem. Le modem est en paille (c’est dommage), mais il est fonctionnellement très bien fournit sur le contrôle de toute la pile réseau.

Récupérer l’accès au back office Magento

Pourquoi ce genre d’informations est toujours difficile à trouver ?! C’est chiant, vraiment, surtout lorsque pour des applications basées sur Magento, je suis du côté de l’administrateur/hébergeur et non du côté du développeur, donc je n’ai aucune idée de la manière dont fonctionne le bouzin (et oui ce truc est bien une machine à vapeur)

Bref, voici une requête SQL (à adapter) permettant d’ajouter un nouvel administrateur et donc retrouver l’accès au back office de Magento.

-- disable old admins
UPDATE admin_user SET is_active=0;

-- creating a new user
INSERT INTO admin_user(firstname, lastname, email, username, password, created)
	VALUES('Lord', 'Veovis', 'got@is-here.fr', 'veovis', CONCAT(MD5(CONCAT('rr', 'my new password')), ':rr'), NOW());

-- granting the new user as GOD
INSERT INTO admin_role(parent_id, tree_level, role_type, user_id, role_name)
	SELECT (SELECT role_id FROM admin_role WHERE role_name = 'Administrators'), 2, 'U', LAST_INSERT_ID(), 'veovis';

-- only if the domain name has changed.
-- WARNING: config_id controlling domain name in plain an secure http are probably not static and different on your deployment
UPDATE core_config_data SET value='http://new-domaine.name.fr/' WHERE config_id IN (3,4);

Stocker les certificats OpenVPN dans le magasin de certificats de Windows

J’ai très fortuitement découvert une fonctionnalité dans OpenVPN permettant non plus de charger le certificat utilisateur depuis un fichier non chiffré (ou dont le chiffrage présente très peu d’intérêt) mais directement depuis le magasin de certificats Windows, rendant ainsi très difficile l’extraction du certificat une fois installé.

C’est entre autre pratique dans les cas suivants :

  • environnement multi-administrateur
  • vol
  • faible contrôle sur la machine physique

Cela se fait via le paramètre cryptoapicert , et remplace logiquement les paramètres cert  et key . Il s’utilise de la manière suivante :

  • En précisant le sujet du certificat à utiliser : cryptoapicert « SUBJ:Peter Runestig »
  • En précisant l’empreinte du certificat à utiliser : cryptoapicert « THUMB:f6 49 24 41 01 b4 … »

Note : OpenVPN dans sa version 2.3 ne supporte toujours pas les certificats encore exotiques, comme ceux utilisant des courbes elliptiques. Il faudra se limiter à du classique RSA/SHA1.

Redémarrer proprement un hôte Windows hébergeant des vms

Depuis la version 8 de VMware Workstation, Vmware a semble-t-il combiné VMware Workstation avec VMware Server en terme de fonctionnalités. On remarquera d’ailleurs que VMware Server n’est plus mis à jour depuis 2009 et plus supporté depuis 2011 par l’éditeur.
Il est donc possible désormais de démarrer des machines virtuelles en même tant que l’hôte. Il n’est plus nécessaire d’ouvrir VMware Workstation à chaque fois, il n’est plus nécessaire d’ouvrir une session et de la laisser ouverte tant qu’on en a besoin. C’est très pratique cependant s’il est possible d’avoir un démarrage automatique, il n’y a pas d’arrêt automatique autre qu’un arrêt brutal des machines virtuelles lorsque l’hôte s’arrête. Du moins il n’y a pas d’interface graphique ni de documentation officielle permettant de paramétrer ce point.

Voici donc la documentation non-officielle pour paramétrer l’auto-stop. Je n’ai pas poussé afin de savoir s’il était possible de configurer l’auto-stop par vm ni qu’elles sont toutes les possibilités, mais je pense que la méthode décrite ci-dessous suffira.

  • Éditez le fichier %ProgramData%\VMware\hostd\vmAutoStart.xml
  • Remplacez PowerOff  par Suspend  dans le nœud stopAction . Cela aura pour effet de suspendre les vms à l’arrêt de l’hôte. Shutdown  marche peut-être également mais je ne l’ai pas testé.
  • Redémarrez le service gérant les machines virtuelles, éventuellement en prenant soin d’éteindre proprement les machines virtuelles.
  • Le tour est joué.

Au cas où il y aurait des utilisateurs de VMware Workstation sous Linux (y en a-t-il ?), je suppose que la méthode est similaire et que le fichier correspondant se trouve quelque part dans /var/lib .

Mise en garde : C’est peu probable mais VMware pourrait très bien retirer cette fonctionnalités dans une prochaine version ou mise à jour de VMware Workstation.

Installer PHP sous Windows mais sans Apache

Vu l’incapacité de l’équipe PHP à fournir des instructions d’installation correctes (genre à jour) sous Windows (ou le manque de volonté, cela revient au même), et parce qu’Apache sous Windows est une hérésie (alors que sous Linux, c’est simplement de la débilité), je m’atèle à résoudre ce problème.

Installation automatique

Je ne m’étendrai pas beaucoup là-dessus, il suffit de suivre les instructions de cette page. La dernière fois que j’ai tenté cette installation, j’ai eu quelques soucis mineurs, on va dire que pour une install rapide, ça ne marche pas trop mal.

Installation manuelle pour IIS6

A quelques détails près, la procédure à suivre est similaire à celle pour IIS7. Par contre continuer à utiliser II6 et surtout pour autre chose que du .NET, ça craint d’un point de vue sécurité.

Installation manuelle pour IIS7 et +

La procédure est identique en tout point à celle pour IIS Express (plus bas), sauf qu’il faut être admin de la machine et que le bon fichier de configuration se trouve dans C:\Windows\System32\inetsrv\config .
L’avantage néanmoins d’avoir un IIS pas express, c’est qu’il y a une interface graphique, c’est probablement plus simple et safe que de sortir directement le burin.

Je détaillerai prochainement la partie graphique (avec des screen) et complètement cette section.

Installation manuelle pour IIS Express

  • Installez IIS Express si ce n’est pas déjà fait en le téléchargeant ici. C’est la seule étape qui nécessite d’être administrateur de la machine, tout le reste peut être réalisé par un utilisateur lambda.
  • Téléchargez la (dernière de préférence) version de PHP ici en prenant soin de préférer la version non thread-safe (NTS) : http://windows.php.net/download/#php-5.4
  • Décompressez php où vous le souhaitez, par exemple dans %ProgramFiles(x86)%\PHP 5.4.17  si vous êtes administrateur ou bien dans %USERPROFILE\AppData\Local\PHP 5.4.17 s’il s’agit d’une installation locale à un utilisateur particulier. Pour la suite de la procédure, on considèrera %INSTALL% %INSTALL%  comme le répertoire d’installation de PHP.
  • Éditez le fichier %USERPROFILE%\Documents\IISExpress\config\applicationhost.config de l’utilisateur souhaitant bénéficier de PHP sur sa plateforme IIS Express
  • Cherchez le nœud fastCgi  dans le nœud system.webServer  et ajoutez-y le nœud suivant : <application fullPath= »%INSTALL%\php-cgi.exe » />
  • Ajoutez ensuite le handler suivant (nœud à ajouter dans le nœud handlers) <add name= »php5″ path= »*.php » verb= »* » modules= »FastCgiModule » scriptProcessor= »%INSTALL%\php-cgi.exe » resourceType= »File » /> . Attention, ce handler doit être ajouté avant les handlers génériques, en particuliers les handlers suivants : StaticFileModule  et les ExtentionlessUrl* .
  • Éditez le fichier %INSTALL%\php.ini  en prenant comme base le fichier php.ini-production  (par exemple) qui se trouve dans le même répertoire
    • Fixez le path comme suit : include_path = « .;%INSTALL%\ext »
    • Fixez cgi.force_redirect = 0
    • Dans la section [Date] , fixez le timezone comme suit : date.timezone = Europe/Paris
    • Spécifiez la variable ext à ext
    • [Optionnel] Mettez la variable short_tags  à On
  • C’est normalement terminé, il n’y a plus qu’à tester le bon fonctionnement de PHP avec le classique <?php phpinfo();

Note

L’équipe PHP ne propose que des builds 32 bits. Il y a des builds 64 bits, mais elles ne sont actuellement pas considérées comme stable.

Monter un tunnel IPSec sous Linux

Ce billet détaille comment monter un tunnel IPSec sous Linux. C’est à mon sens un must have, principalement parce qu’IPSec est une merde ignoble sous Linux.
Mais avant de procéder plus avant, une question se pose.

Pourquoi IPSec ?

Actuellement je souhaite relier :

  • plusieurs serveurs sur Internet
  • plusieurs réseau locaux (celui du bureau et ceux des machines virtuelles derrières le routeur qui détient l’IP publique)

Voici mes possibilités pour réaliser ce « gros » réseau :

  • créer plein de règles nat sur chaque routeurs.
    • c’est pénible à maintenir
    • oblige à reconfigurer les services pour n’utiliser que certaines plages de ports, et sans chevauchement d’une machine/vm à l’autre
    • oblige à avoir une feuille avec la table de mapping et utiliser des ports non standards
      Pas géniale comme solution, et les tuyaux ne sont pas sécurisés.
      On peut alors reconfigurer tous les services pour protéger chaque flux, mais la manière de faire et la possibilité dépendent du service. Désormais on a la plupart des tuyaux qui sont sécurisés et encore plus de maintenance.
  • mettre en place OpenVPN qui répond entièrement à la problématique, mais :
    • il est architecturé en étoile (1 serveur, plusieurs clients). On peut aussi installer OpenVPN sur chaque routeur en mode serveur, mais c’est pas mal de configuration.
    • il fonctionne en faisant le transport sur la couche applicative du modèle OSI (les paquets sont encapsulé dans un flux SSL sur du TCP ou UDP), cela induit un peu d’overhead
      C’est la solution qui est actuellement en place chez moi.

Par rapport à OpenVPN, voici les avantages d’IPSec :

  • c’est du P2P, il n’y a pas de notion « serveur », les routeurs se connectent donc en direct
  • comme OpenVPN, la communication entre les 2 pairs est chiffrée
  • il est kernel-space sous Linux et Windows, il est donc moins lourd qu’OpenVPN et ne nécessite aucune installation sous Windows
  • son intégration aux protocoles IPs lui permet d’avoir un overhead plus faible qu’avec OpenVPN, donc une bande passante plus large
  • les tunnels d’OpenVPN semblent saturer vite vers 10Mbps chez moi

Quelle implémentation d’IPSec ?

C’est une question qui ne se pose que pour le monde Unix. Avoir le choix c’est bien, mais dans le cas d’IPSec, c’est un peu compliqué. Côté logiciel, il y a eu FreeSwan, puis OpenSwan, puis StrongSwan ; côté noyau également, il a existé plusieurs implémentations : klips et netkey.
Aujourd’hui, j’ai jeté mon dévolu sur strongswan. Klips n’existe plus et les autres logiciels ne sont plus maintenus depuis plusieurs années, c’est donc quasiment un choix par défaut, mais également pour la négociation par certificats que strongswan est le seul à proposer (et que je trouve beaucoup plus simple à configurer que le Pre-Shared Key).

La conf !

Elle est très simple. Voici un exemple fonctionnel ci-dessous qui utilise l’authentification mutuelle par certificat, pour le détail: RTFM, ce dernier est plutôt à jour et bien réalisé.
Je ne détaille pas comment générer les certificats, ce n’est pas le but du billet, et puis j’ai déjà traité ça ici.

config setup
cachecrls=yes
# uniqueids = no

# Add connections here.
conn %default
auto=start
compress=no # ne fonctionne pas
dpdaction=restart
esp=aes128-sha1!
ike=aes128-sha1-modp2048,aes128-sha1-modp1024!
rightallowany=yes
left=2001:41d0:fe1c:1300:20c:29ff:fe99:fbf
#left=%any
leftauth=pubkey
rightauth=pubkey
leftid="C=FR, O=Kveer, OU=Kveer IPSec Services, CN=Tefnout"
leftca="C=FR, O=Kveer, OU=Kveer IPSec Services, CN=Kveer IPSec CA"
rightca=%same
leftcert=tefnout.crt
leftfirewall=no
mobike=no
keyexchange=ikev2

conn illidan
right=illidan.kveer.fr
rightid="C=FR, O=Kveer, OU=Kveer IPSec Services, CN=Illidan"
leftsubnet=192.168.3.0/24
rightsubnet=192.168.14.128/27
# les règles iptables à ajouter.
iptables -A INPUT -p udp -m multiport --dport isakmp,ipsec-nat-t -m comment --comment ipsec -j ACCEPT
iptables -A INPUT -p esp -j ACCEPT
iptables -t nat -I POSTROUTING 1 -m policy --dir out --pol ipsec -j ACCEPT

La dernière règle iptables est à ne pas oublier si l’on souhaite faire fonctionner le tunnel pour les routeurs eux-mêmes. Dans le cas d’un tunnel IPv4, ça ne marchera pas, dans le cas d’un tunnel IPv6, le kernel va générer un Oops (la règle est obligatoire sous peine de non-sens, mais cela n’autorise pas le kernel à planter, c’est bien un bug, en attente de correction)

Intégration des modules vmware à linux

Il n’y a pas si longtemps, j’avais écrit un billet permettant de compiler soit-même les quelques modules VMware fournit dans les VMware Tools.
J’ai pu tester ce script à plusieurs reprises d’ailleurs, à chaque mise à jour de kernel en fait, et ça dépanne bien.
Il y a juste un patch, celui concernant le module vmci , qui est à faire manuellement. Pour une raison qui m’échappe encore (je n’ai pas trop cherché non plus, mais je penche vers un problème d’espaces), le copier/coller depuis le script ne fonctionne pas.

Et bien ce billet annonce la bonne nouvelle : ce script va faire partie des vieilleries car depuis la branche 3.9 du noyau Linux, ces modules font désormais partie intégrante du noyau.
Voici le nom des modules (j’en profite également pour rappeler l’existence du pilote vmxnet3 ):

  • CONIG_VMWARE_VSOCKETS
  • CONFIG_VMWARE_VMCI_VSOCKETS
  • CONFIG_VMWARE_BALLOON
  • CONFIG_VMWARE_VMCI
  • CONFIG_VMXNET3

Les re-voici dans le menuconfig, pour vous faciliter la recherche :

 

* Device Drivers --->
** Misc Devices --->
*** \[*\] VMware Balloon Driver
*** \[*\] VMware VMCI Driver
** \[*\] Network device support --->
*** VMware VMXNET3 ethernet driver
* \[*\] Networking support --->
** Networking options --->
*** <*> Virtual Socket protocol
*** <*> VMware VMCI transport for Virtual Sockets

Adieu les vmware tools sous Linux (et sans regrets) !

IPv6 sous Linux

Autant sous Windows l’IPv6 se configure tout seul, autant sous Linux, c’est une autre histoire, en particulier si ce dernier fait office de routeur IPv6 et qu’on a configuré proprement le pare-feu (ou alors je n’ai rien capté, c’est aussi possible).

Il y a deux aspects à voir, tout d’abord lorsque l’OS sert de routeur IPv6 (qu’il soit ou pas routeur IPv4 n’importe pas), c’est à dire lorsque net.ipv6.conf.default.forwarding = 1 , ce dernier n’écoute plus les Router Advertisement, même si net.ipv6.conf.default.accept_ra = 1  et par conséquent, l’IPv6, le masque de sous-réseau ainsi que les routes sont à configurer manuellement. Cela peut surprendre de prime abord.

Le second aspect se situe au niveau de la découverte de ses voisins. En IPv4, cela se fait avec des paquets ARP, alors qu’en IPv6, cela se fait avec des paquets ICMPv6.
Pour rappel, ARP est un protocole de lien, ce n’est pas encapsulé dans un paquet IP.
D’un point de vue du pare-feu, c’est pratique car alors que iptables ne peux filtrer des paquets ARP, puisque ces derniers n’ont rien à voir avec la couche IP (c’est le rôle d’ebtables), ip6tables sait parfaitement filtrer des paquets ICMPv6, eux-mêmes étant encapsulés dans un paquet IPv6. Mais il faut y penser lorsqu’on active son pare-feu.
Cela donne par exemple pour Gentoo :

# /etc/conf/net
config_eth0="192.168.3.2/24
2001:41d0:fe1c:1300:20c:29ff:fe99:fbf/56"
routes_eth0="
default via 192.168.3.1
"2000::/3 via fe80::5a98:35ff:fea1:a7ea" # ça ne sert à rien de router autre chose que des adresses internet pour un serveur dédié "normal"

La première chose qu’on fait en général avec le pare-feu, c’est de passer la stratégie par défaut à DROP, en ajoutant le minimum syndical, à savoir un accès SSH notamment si on n’est pas en local sur la machine. Mais faire cette manip coupe également le bon fonctionnement de l’IPv6.

Après analyse avec tcpdump des trames réseau, on en vient à la configuration d’un pare-feu stateful suivante :

# attention: on flush toute la table, et on change la règle par défaut afin de rester connecté
ip6tables -P INPUT ACCEPT
ip6tables -F INPUT
ip6tables -A INPUT -m ctstate --ctstate INVALID -j DROP
ip6tables -A INPUT -m ctstate --ctstate RELATED,ESTABLISHED -j ACCEPT
ip6tables -A INPUT -i lo -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbour-solicitation -m hl --hl-eq 255 -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbour-advertisement -m hl --hl-eq 255 -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT
ip6tables -P INPUT DROP
# vos règles d'accès commencent ici

 

Les deux premières règles permettent un minimum de protection contre des paquets éventuellement forgés.
La troisième permet d’autoriser toute connexion sur la boucle locale.

Les règles 4 et 5 sont celles qui font marcher le schmilblick et permet au système de découvrir ses voisins, en particulier l’adresse MAC de sa passerelle vers internet. Il n’est pas possible de filtrer par adresse source, car les requêtes peuvent être légitimement émises par des adresses locales (fe80::/10 ), multicast (ff00:/8 ) ou unicast routables (2000::/3 ), en conséquences on filtre sur le nombre de sauts, qui doit être de 1 avec le filtre hl (hop limit), sachant que dans le cas d’un serveur dédié, seul les réponses et requêtes de la passerelle sont désirées et légitimes.

Utiliser TLSv1.1 et TLSv1.2 sous nginx

Suite à mes 2 précédents billets sur une bonne configuration SSL de son serveur web :

Alléluia, openssl 1.0.1 est enfin marqué stable pour Gentoo. Avec lui, le support de TLS en version 1.1 et 1.2 arrive, lesquels permettent notamment de corriger la fameuse attaque BEAST.

Pour utiliser ces protocoles sous nginx, il suffit juste de compiler openssl, recompiler nginx, redémarrer nginx et le tour est joué 😉

Et maintenant on arrive à la grosse blague du billet : Chrome, que je considère comme l’un des 3 navigateurs majeurs, avec Internet Explorer et Firefox, ne supporte que TLS en version 1.0, lequel est sensible à l’attaque BEAST. Difficile sous ces conditions de désactiver TLS 1.0 et inférieur.

Ci-dessous le score donné par les tests SSL de Qualys de mon serveur web avec TLS v1.0 désactivé par rapport à avant :

qualys_2013

ERRATA : il semble que Chrome, au moins dans sa version 24.0.1312.57, supporte TLS v1.1.

Construire sa propre autorité de certification

Ce billet explique comment on peut monter une autorité de certification à 2 sous et surtout l’utiliser au quotidien sans pour autant sacrifier les exigences techniques en terme de sécurité.

Avant la technique, remettons en contexte, une autorité de certification doit répondre à au moins ces deux conditions :

  • les clés privées des autorités racines et intermédiaires doivent être tenues secrètes
  • les clés privées des autorités racines et intermédiaires ne doivent pas être perdues, autrement dit, il faut en garder au moins une copie, également secrète ailleurs.

Le mot « secret » est bien entendu à comprendre au sens « chiffré » et non pas caché en loose dans un dossier invisible ou similaire.

Et la contrainte :

  • la solution doit coûter un minimum

D’où la solution suivante, qui rempli toutes les exigences :

  • le stockage se fait sur une Dropbox (ou hébergement cloud similaire)
  • toutes les clés sont chiffrées
  • l’outil utilisé pour la manipulation et génération des certificats sera OpenSSL, qui est open-source et multi-plateforme (vraiment multi-plateforme, c’est à dire Linux, Cygwin et Windows)

Et maintenant la technique !

L’autorité de certification

OpenSSL est assez mal fichu sur la manière de configurer car il va nous falloir un fichier de configuration pour les certificats d’autorité, et un séparé pour les autres type de certificats. Voyons le contenu pour les certificats d’autorité, puisque c’est ce qui nous intéresse maintenant :

oid_section = new_oids

[ new_oids ]
STREET = 2.5.4.9
S = 2.5.4.8
PostalCode = 2.5.4.17

[ distinguished_name ]
C = FR
O = Kveer
OU = Root CA
CN = Kveer Root CA

[ req ]
default_bits = 4096
default_md = sha1
distinguished_name = distinguished_name
reqs_extensions = x509_ca
prompt = no
utf8 = yes

[ x509_ca ]
basicConstraints = critical,CA:true
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid, issuer
keyUsage = critical, keyCertSign, cRLSign
crlDistributionPoints = URI:http://pki.kveer.com/my.crl

Rien d’extraordinaire ici. Le certificat à générer est marqué comme une autorité de certification, on précise son usage : signer des certificats et des listes de révocation de certification (CRL) et on précise le point de distribution des CRLs. On aurait pu également limiter la longueur de la chaîne de certification, je ne l’ai pas fait.
Et le script bash qui va bien :

#!/bin/bash

#cannot use ecdsa as windows nt 5.x does not support it yet.
# prime192v1 seems to be the best one otherwise.

CA_KEY=ca.key
CA_CERT=ca.crt
CONF=kveer-ca.openssl.cnf

if [[ -f ca.key && -f ca.crt ]]; then
echo 'There is already an existing CA root'
exit 1
fi

# password
stty -echo
read -p 'Please enter a password that will be used to encrypt all private keys: ' PASSWD;echo
stty echo

# key generation
openssl genrsa -aes192 -out ca.key -passout pass:"$PASSWD" 4096

# root ca cert generation
openssl req -new -x509 -extensions x509_ca -key ca.key -passin pass:"$PASSWD" -days 7300 -config $CONF -utf8 -out tmp.crt

mkdir certs reqs keys
touch index.txt
echo 01 > serial.txt
echo 00 > crlnumber

J’aurais souhaité utiliser l’algorithme ECDSA qui est pour le moment considéré plus sur, à taille de clé équivalente, mais pour conserver une compatibilité avec Windows NT5.1 (XP et 2003 Server), je suis resté à l’algorithme RSA.

  1. Le script commence par une petite vérification : ça serait dommage d’écraser son certificat racine, n’est-ce pas ?
  2. Il va demander un mot de passe (ce qui évitera de le taper 36 fois), lequel va servir à chiffrer la clé privée. Attention : c’est à ce moment qu’est définit LE mot de passe pour chiffrer toutes vos clés privées, il a donc intérêt à être costaud
  3. Il génère une clé privée, qu’il encode avant d’écrire sur le disque
  4. Il crée et signe un certificat dont la clé privée vient d’être généré
  5. Il crée quelques fichiers nécessaire à l’utilisation du module « ca » de openssl

Une fois le certificat créée, il ne reste plus qu’à déployer la partie publique (sous la forme du fichier ca.crt) vers tous vos postes pour que les futurs certificats émis puisse être validées par les postes clients.

Les certificats d’autorités intermédiaires

Avant de créer un certificat « standard », c’est à dire ceux qui servent à faire du SSL ou du S/MIME pour l’utilisateur final, je recommande vivement de créer encore un certificat d’autorité, signé par le précédent certificat, qu’on appelle alors certificat d’autorité intermédiaire.

La raison est que si par malheur le certificat racine vient à être compromis, vous êtes mort, tout le château de cartes tombe, vous devez repartir de zéro.
En revanche, si c’est un certificat d’autorité intermédiaire qui est compromis, vous êtes dans la merde également, mais il est possible de récupérer le coup en révoquant ce certificat au niveau du CRL de l’autorité racine, puis en recréant ce certificat d’autorité intermédiaire ainsi que de re-signer tous les certificats qu’il aurait pu émettre. Il n’est pas nécessaire de redéployer le certificat racine sur tous les postes, puisqu’il est safe.

Dans le schéma que je propose, la seule faille est la robustesse du mot de passe généré précédemment et d’éviter de le taper sur un poste dpoé au keylogger.
Si on stocke les fichiers sur une Dropbox, on peut considérer que les fichiers sont publics, et bien que cela puisse ne pas être agréable (on aurait la liste des certificats émis, leur type, leur usage…), l’autorité reste sûre tant que les clés privées ne sont pas déchiffrées, donc si le mot de passe utilisé ainsi que l’algorithme de chiffrage (ici : AES 192 bits) sont solides. Etant donné que AES 192bits est plus que solide, que la connaissance des certificats émis n’a que peu d’importance (avec toutes les conséquences que cela entraîne, puisque indirectement j’accepte de dévoiler une partie du réseau), je réduis la faiblesse de mon système au seul mot de passe.

Par ailleurs l’utilisation de certificats d’autorité intermédiaire permet de déléguer la gestion à un tiers ou un collègue à cet seule autorité intermédiaire sans risquer de tout compromettre. Pour rappel, il est possible de créer une autorité intermédiaire qui ne peut pas émettre de certificat d’autorité, en fixant la chaîne de certification à 0 sur cette autorité.

Les scripts maintenant. Tout d’abord le fichier de configuration :

oid_section = new_oids

[ new_oids ]
STREET = 2.5.4.9
S = 2.5.4.8
PostalCode = 2.5.4.17

[ req ]
default_bits = 4096
default_md = sha1
distinguished_name = distinguished_name

[ distinguished_name ]
countryName = Pays
countryName_default = FR
countryName_min = 2
countryName_max = 2

O = Sujet
O_default = Kveer

OU = Unité organisationnelle

CN = Common Name

[ x509_ca ]
basicConstraints = critical,CA:true
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid, issuer
keyUsage = critical, keyCertSign, cRLSign
crlDistributionPoints = URI:http://pki.kveer.com/my.crl

Puis le script :

#!/bin/bash

#cannot user ecdsa because windows nt 5.x does not support it yet.
# prime192v1 seems to be the best one otherwise.

CA_KEY=ca.key
CA_CERT=ca.crt
CONF=kveer-subca.openssl.cnf
CONF_CA=kveer.openssl.cnf

if [[ ! (-f ca.key && -f ca.crt) ]]; then
echo 'There is no CA root'
exit 1
fi

if [ $# < 1 ]; then
echo "$0 certificate_name"
exit 2
fi

# password
stty -echo
read -p 'Please enter a password that will be used to encrypt all private keys: ' PASSWD;echo
stty echo

# key generation
openssl genrsa -aes192 -out tmp.key -passout pass:"$PASSWD" 4096

# root ca cert generation
openssl req -new -extensions x509_ca -key tmp.key -passin pass:"$PASSWD" -config $CONF -utf8 -out tmp.csr
openssl ca -config $CONF_CA -extensions x509_ca -days 3650 -keyfile $CA_KEY -key $PASSWD -cert $CA_CERT -in tmp.csr -out "$1.crt"

if [[ ! -f "$1.crt" || ! -f tmp.key || ! -f tmp.csr ]]; then
echo 'Missing files, stopping here'
exit 3
fi

mv "$1.crt" "certs/$1.crt"
mv tmp.key "keys/$1.key"
mv tmp.csr "reqs/$1.csr"

mkdir -p "$1/certs" "$1/keys" "$1/reqs"
cp "certs/$1.crt" "$1/ca.crt"
cp "keys/$1.key" "$1/ca.key"
cp *.sh "$1/"
cp *.cnf "$1/"
touch "$1/index.txt"
echo '00' > "$1/serial.txt"

En essence, c’est similaire au script précédent, mais ici le fichier n’est pas auto-signé, et un dossier est créer pour préparer la gestion de l’autorité intermédiaire. On notera les quelques vérifications pour éviter tout accident. Ce n’est pas exhaustif, mais ça me convient pour le moment.
Arrivé à cet étape nous avons une autorité de certification racine, ainsi qu’au moins une autorité de certification intermédiaire. On peut s’attaquer au plus intéressant.

Les certificats « serveur » et « client »

Par certificat « serveur » j’entends ceux utilisé pour authentifier le serveur, donc ceux servant pour initier une connexion SSL sur un serveur web, un protocol de messagerie comme SMTP, POP, IMAP en SSL ou TLS, ou encore un serveur FTP.
Et par certificat « client » j’entends ceux utilisés par Outlook pour signer ses messages.
On va avoir un « gros » fichier de configuration pour ces deux types de certificats :

oid_section = new_oids

[ new_oids ]
STREET = 2.5.4.9
S = 2.5.4.8
PostalCode = 2.5.4.17

[ ca ]
default_ca = default_ca

[ default_ca ]
dir = .
new_certs_dir = $dir/certs
certificate = $dir/ca.pem
private_key = $dir/private/ca.key
RANDFILE = $dir/private/.rand
default_days = 365
default_crl_days = 30
default_md = sha1
database = $dir/index.txt # list of all signed certificates
unique_subject = no # allows to generate multiple certs with same subject name
serial = $dir/serial.txt # next cetificate serial
crlnumber = $dir/crlnumber # next crl number
email_in_dn = no # no email in distinguished name
name_opt = ca_default
cert_opt = ca_default
copy_extensions = copy # copy all new extensions from request to certificate
policy = policy

[ policy ]
C = match
O = match
OU = match
CN = supplied

[ x509_server ]
basicConstraints = CA:false
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid, issuer
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
crlDistributionPoints = URI:http://pki.kveer.com/my_kveer_vpn_ca.crl

[ x509_client ]
basicConstraints = CA:false
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid, issuer
keyUsage = digitalSignature, keyAgreement
extendedKeyUsage = clientAuth
crlDistributionPoints = URI:http://pki.kveer.com/my_kveer_vpn_ca.crl

[ req ]
default_bits = 4096
default_md = sha1
distinguished_name = distinguished_name

[ distinguished_name ]
countryName = Pays
countryName_default = FR
countryName_min = 2
countryName_max = 2

O = Sujet
O_default = Kveer

OU = Unité organisationnelle
OU_default = VPN Services

CN = Common Name

Là j’ai pris l’exemple de mon autorité intermédiaire gérant les certificats pour un VPN. On prendra soin de changer le point de distribution des CRL : chaque autorité de certification doit avoir un point de distribution distinct, sinon il ne sert à rien et risque d’invalider les certificats d’une autre autorité.
Le script bash :

#!/bin/bash

#cannot user ecdsa because windows nt 5.x does not support it yet.
# prime192v1 seems to be the best one otherwise.

CA_KEY=ca.key
CA_CERT=ca.crt
CONF=kveer.openssl.cnf
CONF_TMP=_openssl.conf

if [[ ! (-f ca.key && -f ca.crt) ]]; then
    echo 'There is no CA root'
    exit 1
fi

if [ $# -lt 1 ]; then
    echo "$0 -n [certificate_name] -p [pathlen] -c [crlurl] [-e] [-s altsubject, -s...]"
    echo '-e for using an ecdsa key, else an rsa key'
    exit 2
fi

ECDSA=0
REUSE_PKEY=0
REUSE_REQ=0
REGEN=0
ONLY_REQUEST=0
SHA1=0
TEMPLATE=
FILENAME=
declare -a ALTSUBJECT
while getopts 'cen:rs:t:f:1' OPT
do
    case $OPT in
        c)
            ONLY_REQUEST=1
            ;;
        e)
            ECDSA=1
            ;;
        n)
            NAME=$OPTARG
            ;;
        r)
            REGEN=1
            REUSE_PKEY=1
            ;;
        s)
            ALTSUBJECT+=($OPTARG)
            ;;
        1)
            SHA1=1
            ;;
        t)
            TEMPLATE=$OPTARG
            ;;
        f)
            FILENAME=$OPTARG
            ;;
    esac
done

if [ "$FILENAME" = "" ]; then
    FILENAME=$NAME
fi

if [ -z "$NAME" ]; then
    echo 'Use -n to specify the certificate name.'
    exit 6
fi

if [ "$REGEN" -eq 0 -a -f "certs/$FILENAME.crt" ]; then
    echo 'A certificate with the same name exists already.'
    exit 5
fi

if [ -f "keys/$FILENAME.key" ]; then
    read -p 'An existing private has been found. Use it ? [yes|no] ' TEST;echo
    [[ "$TEST" = 'yes' || "$TEST" = 'y' ]] && REUSE_PKEY=1
fi

if [ -f "reqs/$FILENAME.csr" ]; then
    read -p 'An existing certificat request has been found. Use it ? [yes|no] ' TEST;echo
    [[ "$TEST" = 'yes' || "$TEST" = 'y' ]] && REUSE_REQ=1
fi

if [ "$TEMPLATE" = "" ]; then
    TEMPLATE=x509_server
fi

# password
stty -echo
read -p 'Please enter the master password: ' PASSWD;echo
stty echo

# key generation
if [ $REUSE_PKEY -eq 0 ]; then
    if [ $ECDSA -eq 1 ]; then
        openssl ecparam -conv_form compressed -name prime256v1 -genkey | openssl ec -aes128 -out tmp.key -passout pass:$PASSWD
    else
        openssl genrsa -aes192 -out tmp.key -passout pass:"$PASSWD" 2048
    fi
else
    ln -f "keys/$FILENAME.key" tmp.key
fi

if [ $REUSE_REQ -eq 0 ]; then
    # generate conf
    sed -e "s/^CN_default.*/CN_default = $NAME/g" \
        $CONF > $CONF_TMP
    if [ $SHA1 = 1 ]; then
        sed -i -e 's/sha256/sha1/g' $CONF_TMP
    fi
    if [ ${#ALTSUBJECT[@]} -gt 0 ]; then
        dns=0
        ip=0
        
        sed -i -e "/\\[[ $TEMPLATE \\]]/a subjectAltName = @subjectAltName" -e '$a [ subjectAltName ]' $CONF_TMP
        #sed -i "/\[ $TEMPLATE \]/a subjectAltName = @subjectAltName" -i '$a [ subjectAltName ]' $CONF_TMP
        
        
        for s in "${ALTSUBJECT[@]}"
        do
            if [[ $s =~ ^([0-9]+\.){3}[0-9]+$ || $s =~ ^[0-9\:]+$ ]]; then
                let ip++
                sed -i "/\[ subjectAltName \]/a IP.$ip=$s" $CONF_TMP
            else
                let dns++
                sed -i "/\[ subjectAltName \]/a DNS.$dns=$s" $CONF_TMP
            fi
        done
    fi

    # cert generation
    openssl req -new -extensions $TEMPLATE -key tmp.key -passin pass:$PASSWD -config $CONF_TMP -utf8 -out tmp.csr
else
    cp "reqs/$FILENAME.csr" tmp.csr
fi

[[ $ONLY_REQUEST == 0 ]] && openssl ca -config $CONF_TMP -days 3650 -extensions $TEMPLATE -keyfile $CA_KEY -key $PASSWD -cert $CA_CERT -in tmp.csr -out "$FILENAME.crt"

if [[ $ONLY_REQUEST == 0 && ! -f "$FILENAME.crt" || ! -f tmp.key || ! -f tmp.csr ]]; then
    echo 'Something wrong, stopping here'
    exit 3
fi

[[ $ONLY_REQUEST == 0 ]] && mv "$FILENAME.crt" "certs/$FILENAME.crt"
mv tmp.key "keys/$FILENAME.key"
mv tmp.csr "reqs/$FILENAME.csr"

Une seule ligne change en fonction de si l’on souhaite un certificat serveur ou client, c’est celle où la signature du certificat intervient avec le paramètre -extensions, qui indique ou bien la section x509_server pour un certificat serveur, ou bien la section x509_client pour un certificat client.

Les certificats EV

Ces certificats EV, ou extended validation, sont un peu spéciaux, précieux même en ce sens que du point de vue de l’utilisateur, cela rend la barre du navigateur vert, alors qu’avec les autres certificats, le navigateur se content de rendre la barre bleue ou bien d’afficher uniquement un cadenas.

ll s’agit de certificats ayant des informations supplémentaires, tout comme le certificat d’autorité qui l’a émis, dont certains non-référencé par OpenSSL d’ailleurs, donc à rajouter manuellement dans la section new_oids . Mais la liste des certificats d’autorité pouvant émettre des certificats EV est codé en dur dans chaque navigateur, donc même s’il est possible de forger un certificat en tout point identique au niveau de la liste des attributs contenus à un vrai certificat EV, y compris pour un usage interne, le navigateur ne reconnaîtra jamais un certificat que vous avez créée comme EV, il sera considéré comme un certificat standard.

Les autres script

Afin de parfaire l’autorité, d’autres script sont nécessaires.

Script de génération de la liste de révocation de certificats

Cette liste, aussi appelée plus simplement CRL, publiquement accessible et signée, permet d’informer sur les certificats qui ont été prématurément révoqués, pour une raison ou une autre (certificat compromis, perdu, clé privée faible, révocation d’accès…)

#!/bin/bash

# password
stty -echo
read -p 'Please enter the master password: ' PASSWD;echo
stty echo

openssl ca -gencrl -crldays 365 -keyfile ca.key -key $PASSWD -cert ca.crt -config kveer.openssl.cnf | openssl crl -outform DER -out crl.crl

Une fois la liste générée, il faudra la rendre accessible à l’adresse qui a été spécifiée par le paramètre crlDistributionPoints . Ce paramètre est présent dans chaque certificat généré, c’est de cette manière que le point de communication est connu. A noté que la CRL ne contenant que les numéros de séries des certificats émis par une autorité de certification, une CRL est propre à chaque autorité de certification.

Tout comme un certificat, la CRL a une durée de vie, ici fixé à 1 an, dont le but est de forcer régulièrement le programme à récupérer la version à jour de la liste des certificats périmés. Surtout qu’un certificat révoqué ne se fait que lorsqu’un problème de sécurité est rencontré, il est impératif que l’information se propage au plus vite afin de limiter l’utilisation abusive d’un certificat périmé.

Remarque : tous les certificats émis par une même autorité de certification doivent avoir la même adresse dans le paramètre crlDistributionPoints .

Corollaire 1 : Pour une autorité de certification intermédiaire B, son point de distribution est donc celle décidé par l’autorité A ayant signée le certificat intermédiaire, dans mon cas, c’est donc l’autorité racine.

Corollaire 2 : Une autorité intermédiaire B ne peut pas se révoquer elle-même. Son autorité parente A peut révoquer  B et publier un nouveau CRL signé avec la clé de l’autorité parente A.

Script de révocation

Ce script permet de révoquer un certificat émis, c’est-à-dire le rendre invalide avant sa date d’expiration.

#!/bin/bash

CA_KEY=ca.key
CA_CERT=ca.crt
CONF_CA=kveer.openssl.cnf

if [[ ! (-f ca.key && -f ca.crt) ]]; then
    echo 'There is no CA root'
    exit 1
fi

if [[ $# < 1 ]]; then
    echo "$0 certificate_name"
    exit 2
fi

# password
stty -echo
read -p 'Please enter the master password: ' PASSWD;echo
stty echo

openssl ca -revoke certs/$1.crt -keyfile ca.key -key $PASSWD -cert ca.crt -config $CONF_CA

Ce script est inutile tout seul. Une fois un certificat révoqué, la CRL doit être mise à jour afin de publier l’information.

Script d’export au format PFX

Le seul moyen d’importer un certificat avec sa clé privée dans un magasin de certificats géré par Windows est de passer par un package PFX ou PKCS12. Il s’agit d’un standard permettant d’échanger du contenu sensible à l’aide d’un mot de passe.

#!/bin/bash

#cannot user ecdsa because windows nt 5.x does not support it yet.
# prime192v1 seems to be the best one otherwise.

if [[ $# < 1 ]]; then
    echo "$0 certificate_name"
    exit 2
fi

# Generate a random password
#  $1 = number of characters; defaults to 32
#  $2 = include special characters; 1 = yes, 0 = no; defaults to 1
function randpass {
    [ "$2" == "0" ] && CHAR="[:alnum:]" || CHAR="[:graph:]"
    cat /dev/urandom | tr -cd "$CHAR" | head -c ${1:-32}
}

EXPORT=$(randpass 20 0)
echo "Note this import password somewhere:" $EXPORT

# password
stty -echo
read -p "Please enter the password: " PASSWD;echo
stty echo

cat "keys/$1.key" "certs/$1.crt" ca.crt ../ca.crt | openssl pkcs12 -export -name "$1" -passout pass:$EXPORT -passin pass:$PASSWD -aes192 -out "certs/$1.pfx" -CAfile ca.crt

Il suffit de spécifier le nom d’un certificat précédemment généré et le script génère le PFX ainsi que son mot de passe de manière aléatoire.