Écrire un client Bitwarden en python : créer une organisation et une collection

Je suis en train d'écrire un logiciel qui va interagir avec Bitwarden. Il existe plusieurs clients Bitwarden, mais peu sont suffisamment complet pour mon besoin.

Ce billet fait suite au premier billet écrire un client Bitwarden en python : créer, connecter, valider un compte et au deuxième billet écrire un client Bitwarden en python : créer un identifiant dans mon coffre

On va voir comment créer une organisation et une collection.

Une organisation est un espace de stockage où un ensemble d'utilisateur peut partager des éléments.

Une collection (à ne pas confondre avec les répertoires) permet de ranger les éléments et de les partager avec les membres d'une organisation.

Créer une organisation

Avant de créer une organisation, il va falloir créer une clé symétrique. On ne peut pas utiliser la clé symétrique de son coffre, car l'objectif des organisations est de pouvoir partage des informations avec d'autres utilisateurs. Cette clé symétrique va donc être partage avec tous les utilisateurs.

Ne pouvant être stocké en clair dans la base de Bitwarden et ne pouvant accéder aux clés symétriques des autres utilisateurs, cette clé va être chiffrée avec les clés publiques des clés asymétriques de tous les utilisateurs accédant à cette organisation.

Les utilisateurs utiliseront leur clé privée, protégé, pour accéder à la clé symétrique.

C'est pour cela qu'à la création des utilisateurs nous avons créé une clé asymétrique.

Récupérons la clé asymétrique (la variable login ci-dessous provient de la connexion de l'utilisateur) :

private_key = login['PrivateKey']

Cette clé est chiffrée avec la clé symétrique de l'utilisateur. Il faut donc commencer par la déchiffrer comme vu dans le précédent billet.

On génère ensuite une clé symétrique pour cette organisation :

token = token_bytes(64)

Et on la chiffre avec la clé asymétrique précédemment déchiffrée :

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

rsa_key = RSA.importKey(private_key)
cipher = PKCS1_OAEP.new(rsa_key).encrypt(token)
b64_cipher = b64encode(cipher).decode()

Le secret est mis dans une chaine avec le type 4 pour préciser que la donnée est chiffrée par une clé asymétrique :

encoded_key = f"4.{b64_cipher}"

Pour créer l'organisation il nous manque :

  1. une adresse courriel de facturation (je suppose que c'est utile uniquement sur la plateforme officielle)
  2. le nom de l'organisation chiffrée avec la clé symétrique de cette organisation
  3. le nom de la collection par défaut
data = {"key": encoded_key,
            "collectionName": encoded_organization_name,
            "name": collection_name,
            "billingEmail": email,
            "planType": 0,
        }

Il faut faire un POST avec ce payload sur l'URL ''https://localhost:8001/api/organizations' pour créer l'organisation. Dans l'entête il faut rajouter les informations d'authentification :

{'Authorization': f'Bearer {login["access_token"]}'}

Créer une collection

La création d'une collection est assez simple.

Il faut juste chiffrer avec la clé symétrique de la collection le nom de la collection.

 data = {"groups": [],
               "name": encrypted_name
              }

L'URL pour créer la collection est construite à partir de l'ID de l'organisation : f'https://localhost:8001/api/organizations/{organization_id}/collections'.

Il faut faire un POST avec ce payload sur cette URL. Dans l'entête il faut rajouter les informations d'authentification :

{'Authorization': f'Bearer {login["access_token"]}'}

Invitée un autre utilisateur à cette organisation

L'invitation de l'utilisateur ne pose par de problème (ici je ne m'occupe pas de la gestion des droits) :

email = 'other@gnunux.info'
data = {'emails': [email],
             'collections': None,
             'accessAll': True,
             'type': 2,
             }

L'URL pour invité un utilisateur à la collection est construite à partir de l'ID de l'organisation : f'https://localhost:8001/api/organizations/{organization_id}/users/invite'.

Il faut faire un POST avec ce payload sur cette URL. Dans l'entête il faut rajouter les informations d'authentification :

{'Authorization': f'Bearer {login["access_token"]}'}

Confirmer l'invitation

L'utilisateur devrait alors accepter cette invitation. Un courriel est envoyé à l'utilisateur qui devra cliquer sur le lien et s'authentifier sur Bitwarden.

Il est possible d'automatiser ce processus comme lors de la création d'un utilisateur mais cette fonctionnalité ne m'intéresse pas.

Une fois que l'utilisateur a accepté l'invitation, il faut confirmer l'invitation.

La confirmation consiste principalement à chiffrer la clé symétrique de cette organisation avec la clé publique de la clé asymétrique de l'utilisateur.

Le plus simple pour récupérer cette clé publique, et de récupérer les informations de l'organisation.

Il suffit pour cela de faire un GET sur l'URL f'https://localhost:8001/api/organizations/{organization_id}/users' et de récupérer la réponse dans la variable "users". Dans l'entête il faut rajouter les informations d'authentification :

{'Authorization': f'Bearer {login["access_token"]}'}

À partir de ces informations on va rechercher le numéro utilisateur :

> for user in users['Data']:
>     if user['Email'] == email:
>         user_id = user["UserId"]
>         break

Et nous allons récupérer la clé publique.

Il suffit pour cela de faire un GET sur l'URL f'https://localhost:8001/api/users/{user_id}/public-key' et de récupérer la réponse dans la variable "user_public_key". Dans l'entête il faut rajouter les informations d'authentification :

{'Authorization': f'Bearer {login["access_token"]}'}

Et on retrouve la clé publique :

public_key = b64decode(user_public_key['PublicKey'])

Et on chiffre la clé symétrique de l'organisation avec cette clé publique de la même façon qu'on a chiffrée au-dessus pour la sienne.

Enfin on construit le payload :

data = {"key": key}

L'URL pour confirmer l'invitation d'un utilisateur à la collection est construite à partir de l'ID de l'organisation et de l'utilisateur : f'https://localhost:8001/'api/organizations/{organization_id}/users/{user_id}/confirm'.

Il faut faire un POST avec ce payload sur cette URL. Dans l'entête il faut rajouter les informations d'authentification :

{'Authorization': f'Bearer {login["access_token"]}'}

Pour finir ...

Avec les informations contenues dans ces trois billets, vous devriez être capable d'intéragir avec Bitwarden.

Pour comprendre le fonctionnement de Bitwarden je me suis servi :

  1. de la documentation de l'API de Bitwarden
  2. l'implémentation rust de Bitwarden
  3. de l'implémentation officielle du client en typescript/javascript
  4. d'une implémentation d'un client en python
  5. d'une implémentation d'un client en ruby
  6. d'une implémentation d'un client en rust

Raconter son circuit touriste avec Piwigo

Je pars bientôt visiter un pays étranger pendant deux semaines. Les enfants resteront en france, donc je voulais mettre en place un site permettant de diffuser des photographies et nos impressions. Voilà mes besoins : tout doit être privé donc accessible uniquement par mot de passe ; le site est  […]

Lire la suite

Piratons la démocratie

Le parti pirate présente un candidat dans la 2eme circonsccription de Côte d'Or. Une réunion d'information est prévu le mercredi 23 mai 2012. Voici l'annonce : Le bar l'Antre II Mondes de Dijon nous a proposé d'animer une réunion de présentation du Parti Pirate le mercredi 23 mai prochain à 18h30.  […]

Lire la suite

Edenwall est mort ... vive Cadoles

Comme je disais il y a un peu plus de 3 ans, je ne parle pas facilement de mon activité professionnelle (1) sur ce site. A l'époque je rejoignais l'entreprise INL (devenu ensuite EdenWall) (2). Durant ces 3 ans 1/2 j'ai travaillé sur le projet libre EOLE de l'Éducation Nationale (3). EOLE est un  […]

Lire la suite

Mécanisme de "clef obligatoire" par utilisateur avec dconf (GNOME 3)

Contrairement à GNOME 2, GNOME 3 n'utilise plus GConf pour gérer les éléments de configuration. Maintenant c'est Gsettings. Gsettings est en réalité une API de configuration. Le stockage des configurations se fait par un backend. Sous GNU/Linux, le backend est dconf. Avec GConf il était facile  […]

Lire la suite

Dernière version RC de Gaspacho avant la version 0.1

Je viens de mettre en ligne la dernière version RC de Gaspacho 0.1. Gaspacho est une application libre (GNU/GPL v3) offrant à l'administrateur la possibilité de gérer, de façon centralisée, la configuration de l'environnement de travail des utilisateurs d'une entité. J'encourage tout le monde à  […]

Lire la suite

"Celle-ci sera centrée sur Ubuntu, afin de ne pas faire doublon avec la COAGUL"

CE TEXTE NE REPRÉSENTE QUE CELUI QUI L'A ÉCRIT Voici la justification principale lors de la réunion de création de l'association Ubuntu-Dijon (1) : "Celle-ci sera centrée sur Ubuntu, afin de ne pas faire doublon avec la COAGUL". En effet, j'avais essayé de convaincre les autres  […]

Lire la suite

Les règles dans Gaspacho ?

Une règle, dans Gaspacho, est simplement (pour schématiser) un libellé. Par exemple "Configuration du serveur proxy manuelle" est une règle. Évidement, la règle en elle même ne sert a rien. Il faut lui associer des éléments de configuration. Avant d'expliquer comment cela fonctionne, voici  […]

Lire la suite

Haut de page