Unification souveraine PKCS#11 + SSH-agent + Pageant + CNG/Smartcard
Un exécutable Windows unique qui unifie quatre fonctions traditionnellement séparées :
- Module PKCS#11 complet (Firefox, OpenSC, ssh -I)
- Agent SSH compatible OpenSSH/Git/VS/WSL
- Serveur Pageant compatible PuTTY/plink/pscp
- Orchestrateur CNG/KSP sécurisé pour smartcards
- Philosophie
- Architecture
- Modes d'exécution
- Installation
- Configuration
- Protocoles supportés
- Gestion du PIN
- Backend CNG
- Sécurité
- Compatibilité
- Export de clés publiques
- Tray Icon
- WSL2 Support
- Détection de conflits
- Dépendances
- Limites connues
- Roadmap
Souverain. Aucune dépendance au CRT. Toutes les opérations mémoire passent par RtlCopyMemory, RtlZeroMemory, RtlEqualMemory (FreeCRT.h). Unicode partout (Win32 natif). Aucun malloc, memcpy, strlen, printf.
Sécurisé. Les clés privées ne sont jamais exportées. Aucun PIN ne transite. CNG/KSP gère l'UI PIN native Windows. Isolation stricte service ↔ userland via pipes sécurisés.
Minimaliste. Un seul binaire. Aucune DLL externe. Pas de registry bloat. Installation simple (regsvr32 ou -install).
Polyvalent. Support simultané de PKCS#11, SSH-agent, Pageant, et WSL2 dans le même processus.
┌──────────────────────────────────────────────────────────────┐
│ Clients (Git, VS, WSL, OpenSSH, PuTTY, Firefox) │
└────────────────────────┬─────────────────────────────────────┘
│
┌───────────────┼───────────────┬─────────────────┐
│ │ │ │
SSH-agent Pageant (WM_COPYDATA) PKCS#11 WSL2 (TCP)
│ │ │ │
v v v v
┌──────────────────────────────────────────────────────────────┐
│ Service Stub (session 0, SYSTEM) │
│ - Accepte connexions sur \\.\pipe\openssh-ssh-agent │
│ - Crée pipe interne par client (GUID unique) │
│ - Lance helper userland avec token interactif │
│ - Forwarde messages sans manipuler de secrets │
└────────────────────────┬─────────────────────────────────────┘
│ lancé par le service
v
┌──────────────────────────────────────────────────────────────┐
│ Helper Userland (session interactive) │
│ - Connecte au pipe interne │
│ - Décode protocole SSH-agent/Pageant │
│ - Invoque CNG/KSP pour signature │
│ - UI PIN native Windows (pas de relay) │
│ - Renvoie signature au service │
│ - Fenêtre Pageant cachée pour WM_COPYDATA │
│ - Listener TCP 127.0.0.1:10022 pour WSL2 │
│ - Tray icon avec menu contextuel │
└────────────────────────┬─────────────────────────────────────┘
│
v
┌──────────────────────────────────────────────────────────────┐
│ CNG/KSP Backend │
│ - NCryptSignHash avec PKCS#1/PSS padding │
│ - Enumération certificats Windows Store │
│ - Filtrage SmartCardOnly / AllowedKSP │
│ - Support RSA + ECDSA (P-256, P-384, P-521) │
│ - Support EdDSA (Ed25519, Ed448) │
│ - Support Brainpool (P256r1, P384r1, P512r1) │
│ - Cache clés + providers (4h timeout) │
└──────────────────────────────────────────────────────────────┘
Chargé par :
ssh -I ssh-agent.exe user@host- Firefox (Security Devices → Load PKCS#11 Module)
pkcs11-tool --module ssh-agent.exe --list-objects
Expose les exports PKCS#11 standards :
C_Initialize,C_Finalize,C_GetInfoC_GetSlotList,C_GetSlotInfo,C_GetTokenInfoC_GetMechanismList,C_GetMechanismInfoC_OpenSession,C_CloseSession,C_Login,C_LogoutC_FindObjectsInit,C_FindObjects,C_FindObjectsFinalC_GetAttributeValueC_SignInit,C_SignC_VerifyInit,C_VerifyC_DecryptInit,C_DecryptC_GenerateRandom,C_SeedRandom
Mécanismes supportés (14 au total) :
CKM_RSA_PKCS(raw avec padding)CKM_RSA_X_509(raw sans padding)CKM_SHA1_RSA_PKCS(legacy ssh-rsa)CKM_SHA256_RSA_PKCS(rsa-sha2-256)CKM_SHA384_RSA_PKCS(rsa-sha2-384)CKM_SHA512_RSA_PKCS(rsa-sha2-512)CKM_SHA256_RSA_PKCS_PSS(RSA-PSS SHA-256)CKM_SHA384_RSA_PKCS_PSS(RSA-PSS SHA-384)CKM_SHA512_RSA_PKCS_PSS(RSA-PSS SHA-512)CKM_ECDSA(raw)CKM_ECDSA_SHA1(legacy)CKM_ECDSA_SHA256(ecdsa-sha2-nistp256/384/521)CKM_ECDSA_SHA384CKM_ECDSA_SHA512
ssh-agent.exe- Crée le pipe
\\.\pipe\openssh-ssh-agenten session utilisateur - Implémente le protocole SSH-agent
- Support multi-client (max 16 connexions simultanées)
- Lance automatiquement le serveur Pageant
- Lance automatiquement le listener WSL2 (127.0.0.1:10022)
- Utilise CNG/KSP directement (pas de service)
- UI PIN dans la session courante
- Cache PIN optionnel (configurable)
- Tray icon avec statistiques temps réel
Compatible avec :
- Git for Windows (
set SSH_AUTH_SOCK=\\.\pipe\openssh-ssh-agent) - Visual Studio
- WSL (via npiperelay ou socat)
- WSL2 (via TCP 127.0.0.1:10022)
- OpenSSH for Windows
- PuTTY, plink, pscp, psftp (via Pageant)
ssh-agent.exe -install
net start SROSSHAgentCNG- Tourne en session 0 (SYSTEM)
- Accepte les connexions sur pipe global
- Crée un pipe interne par client (sécurisé par SID)
- Lance un helper userland avec
CreateProcessAsUserW - Forwarde les messages sans toucher aux secrets
- Pool de helpers avec timeout 4h (réutilisation automatique)
- Éviction LRU si pool plein
Avantages :
- UI PIN dans la session utilisateur (pas en session 0)
- Compatible environnements durcis
- Isolation stricte service ↔ crypto
- Multiplexage multi-utilisateurs
ssh-agent.exe -useragent -pipe \\.\pipe\ssh-ksp-helper-{GUID}Lancé automatiquement par le service :
- Connecte au pipe interne
- Traite les requêtes SSH-agent/Pageant
- Invoque
NCryptSignHash(UI PIN native) - Renvoie la signature au service
- Lance Pageant (fenêtre cachée)
- Lance listener WSL2 (127.0.0.1:10022)
- Affiche tray icon (mode SERVICE)
- Se termine après timeout ou déconnexion
regsvr32 ssh-agent.exeCrée les clés :
HKLM\SOFTWARE\San@sro Inc\PKCS11-SSH-AgentHKCU\SOFTWARE\San@sro Inc\PKCS11-SSH-AgentHKCU\SOFTWARE\Mozilla\Firefox\PKCS11Modules\SROSSHAgent
ssh-agent.exe -install
net start SROSSHAgentCNGAjouter au ~/.bashrc ou ~/.zshrc :
# --- Pont SSH Agent Windows (TCP -> Unix Socket) ---
export SSH_AUTH_SOCK="$HOME/.ssh/agent.sock"
# Vérifier si le socket est déjà géré par un socat actif
if ! pgrep -u $USER socat > /dev/null || [ ! -S "$SSH_AUTH_SOCK" ]; then
# Nettoyage préventif
rm -f "$SSH_AUTH_SOCK"
# Lancement du bridge en arrière-plan
# Note: Utiliser 127.0.0.1 si mode 'mirrored'
# sinon l'IP du host (ex: 192.168.99.x)
socat UNIX-LISTEN:"$SSH_AUTH_SOCK",fork,unlink-early \
TCP:127.0.0.1:10022 > /dev/null 2>&1 &
firegsvr32 /u ssh-agent.exe
ssh-agent.exe -removeClé : HKLM\SOFTWARE\San@sro Inc\pkcs11-cng ou HKCU\SOFTWARE\San@sro Inc\pkcs11-cng
| Valeur | Type | Description |
|---|---|---|
StoreName |
REG_SZ | "MY", "Root", etc. (défaut: "MY") |
StoreLocation |
REG_SZ | "CurrentUser" ou "LocalMachine" |
Mode |
REG_SZ | "All" ou "SmartCard" |
SmartCardOnly |
REG_DWORD | 1 = filtrer uniquement smartcards |
AllowedKSP |
REG_SZ | Liste de KSP autorisés (séparés par ";") |
LogLevel |
REG_DWORD | 0=off, 1=error, 2=info, 3=debug |
Exemple :
StoreName = "MY"
StoreLocation = "CurrentUser"
SmartCardOnly = 1
AllowedKSP = "Microsoft Smart Card Key Storage Provider;YubiKey Smart Card Key Storage Provider"
LogLevel = 2
Requête :
[type=11]
Réponse :
[type=12][count][key_blob_1][comment_1][key_blob_2][comment_2]...
key_blob RSA :
[len]["ssh-rsa"][len][exponent][len][modulus]
key_blob ECDSA :
[len]["ecdsa-sha2-nistp256"][len]["nistp256"][len][point]
key_blob EdDSA :
[len]["ssh-ed25519"][len][point]
Requête :
[type=13][len][key_blob][len][data][flags]
Flags :
0x00: ssh-rsa (SHA-1, legacy)0x02: rsa-sha2-2560x04: rsa-sha2-512
Réponse :
[type=14][len][signature_blob]
signature_blob :
[len]["rsa-sha2-256"][len][signature_data]
Compatible PuTTY via WM_COPYDATA :
- Client crée une mémoire partagée via
CreateFileMapping - Écrit la requête SSH-agent au format standard
- Envoie
WM_COPYDATAà la fenêtre "Pageant" - Lit la réponse dans la mémoire partagée
Format mémoire partagée :
[uint32 length][SSH-agent payload]
Listener TCP sur 127.0.0.1:10022 :
- Accept multi-client (max 16 connexions)
- Protocole SSH-agent standard sur TCP
- Bind localhost uniquement (sécurité)
- Thread par connexion
- Forwarding vers
handle_ssh_message()
Windows gère entièrement le PIN via CNG/KSP et le minidriver de la smartcard.
Le module ne stocke jamais le PIN et ne le voit jamais transiter :
- En mode standalone : NCryptSignHash déclenche l'UI PIN native
- En mode service : Le helper userland (session interactive) invoque NCryptSignHash, l'UI PIN s'affiche dans la session utilisateur
- Le service stub ne fait que du forwarding transparent (passthrough) entre pipes
Cache PIN : Géré automatiquement par Windows/minidriver (pas besoin de cache applicatif).
Flags NCrypt :
- Cache PIN actif + hit :
NCRYPT_SILENT_FLAG(pas d'UI) - Cache PIN actif + miss : Pas de flag (UI affichée)
- Cache PIN désactivé : Pas de flag (UI toujours affichée)
- Si
SILENT_FLAGéchoue : Retry automatique avec UI
Cache clés (timeout 4h) :
- Indexé par thumbprint SHA-1
- Stocke
CNG_KEY_INFO(handle, provider, container) - Éviction automatique si expiré
- Nettoyage manuel via tray icon
Cache providers (timeout 4h) :
- Indexé par nom KSP
- Stocke
NCRYPT_PROV_HANDLE - Évite les appels répétés à
NCryptOpenStorageProvider
cng_store_enum_certificates(cfg, callback, user_data);Filtre :
- Clés privées disponibles
- KSP autorisés (si
SmartCardOnly) - Clés non-exportables (si
SmartCardOnly)
cng_sign_hash(key_info, mechanism, hash, hash_len, signature, &sig_len);Mécanisme → Padding :
CKM_RSA_PKCS→BCRYPT_PAD_PKCS1CKM_SHA256_RSA_PKCS→BCRYPT_PAD_PKCS1+BCRYPT_SHA256_ALGORITHMCKM_SHA256_RSA_PKCS_PSS→BCRYPT_PAD_PSS+ salt size = hash sizeCKM_ECDSA_SHA256→ Pas de padding (signature brute)
RSA :
cng_cert_get_public_key(cert, modulus, &mod_len, exponent, &exp_len);ECDSA :
cng_cert_get_ec_params(cert, params, ¶ms_len); // OID courbe
cng_cert_get_ec_point(cert, point, &point_len); // Point publicCourbes supportées :
- NIST:
nistp256(OID: 1.2.840.10045.3.1.7),nistp384(1.3.132.0.34),nistp521(1.3.132.0.35) - Brainpool:
brainpoolP256r1,brainpoolP384r1,brainpoolP512r1 - EdDSA:
ed25519(OID: 1.3.101.112),ed448(1.3.101.113)
Support authentification Active Directory :
cng_extract_upn_from_certificate(cert, upn, upn_size);Extrait l'extension szOID_NT_PRINCIPAL_NAME pour l'utiliser comme commentaire SSH.
Jamais exportées. Toutes les opérations cryptographiques sont déléguées à CNG/KSP. NCryptSignHash est appelé avec le handle de clé, jamais avec la clé elle-même.
Géré exclusivement par Windows (CNG/KSP/minidriver).
Le module ne stocke jamais le PIN et ne le voit jamais transiter :
- Le PIN n'est jamais transmis au module PKCS#11
- L'UI PIN est affichée par le minidriver de la smartcard
- Le cache PIN est géré automatiquement par Windows/minidriver
- En mode service : le helper userland (session interactive) reçoit l'UI PIN
Mode service (passthrough pur) : Le service stub ne fait QUE du forwarding transparent :
- Client → Service → Helper (forward message SSH-agent)
- Helper → Service → Client (forward réponse SSH-agent)
- Le service ne parse jamais le contenu
- Le service ne voit jamais : PIN, hash, signature, clé
Pipes sécurisés. Chaque pipe interne est :
- Généré avec un GUID unique
- Créé avec
FILE_FLAG_FIRST_PIPE_INSTANCE - DACL permettant uniquement l'utilisateur courant
Le helper userland invoque CNG/KSP dans la session interactive → UI PIN native.
Logs Unicode. Tous les événements sont journalisés via utils_log() :
- Connexions client
- Énumération clés
- Requêtes signature
- Erreurs CNG/KSP
- Conflits agents
Emplacement : OutputDebugString + fichier optionnel (utils_set_log_file()).
| Environnement | Mode | Status |
|---|---|---|
| OpenSSH for Windows | Standalone / Service | ✓ |
| Git for Windows | Standalone / Service | ✓ |
| Visual Studio | Standalone / Service | ✓ |
| WSL (npiperelay) | Standalone / Service | ✓ |
| WSL2 (TCP) | Standalone / Service | ✓ |
| PuTTY / plink / pscp | Pageant | ✓ |
| Firefox | PKCS#11 | ✓ |
| OpenSC / pkcs11-tool | PKCS#11 | ✓ |
| ssh -I (OpenSSH) | PKCS#11 | ✓ |
| Environnements durcis | Service stub | ✓ |
| SmartCard GIDS | CNG/KSP | ✓ |
| SmartCard PIV | CNG/KSP | ✓ |
| YubiKey | CNG/KSP | ✓ |
| Nitrokey | CNG/KSP | ✓ |
ssh-agent.exe -exportkey [output.pub]- Ouvre
CryptUIDlgSelectCertificateFromStore - Affiche tous les certificats avec clés privées
- Permet sélection visuelle
- Extrait clé publique (RSA/ECDSA/EdDSA)
- Encode au format SSH wire
- Génère deux formats :
- OpenSSH (authorized_keys)
- RFC4716 (SSH2 Public Key)
OpenSSH :
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5... user@domain.com
RFC4716 :
---- BEGIN SSH2 PUBLIC KEY ----
Comment: "user@domain.com"
AAAAB3NzaC1yc2EAAAADAQABAAABAQC5ABCDEF...
---- END SSH2 PUBLIC KEY ----
- RSA (toutes tailles)
- ECDSA NIST (P-256, P-384, P-521)
- ECDSA Brainpool (P256r1, P384r1, P512r1)
- EdDSA (Ed25519, Ed448)
Ordre de priorité pour le commentaire :
- UPN (szOID_NT_PRINCIPAL_NAME)
- CN (Common Name)
- Subject DN complet
TRAY_MODE_USERLAND (vert) :
- Agent standalone actif
- Tooltip : "SRO SSH-Agent (Userland)"
TRAY_MODE_SERVICE (bleu) :
- Helper lancé par le service
- Tooltip : "SRO SSH-Agent (Service Helper)"
Tooltip dynamique :
SRO SSH-Agent (Userland)
12 keys, 3 clients
Mise à jour :
- Toutes les 5 secondes
- À chaque connexion/déconnexion client
- Au flush du cache
Show Keys... : Dialogue listant toutes les clés disponibles
═══════════════════════════════════════════════
SRO SSH-Agent - Available Keys
═══════════════════════════════════════════════
[01] RSA-2048 - user@domain.com
[02] ECDSA-nistp256 - alice@company.local
[03] EdDSA-Ed25519 - bob@example.org
═══════════════════════════════════════════════
Total: 3 keys
💡 Tip: Use 'Export Public Key' to copy SSH format
Export Public Key... : Lance le dialogue de sélection et copie au clipboard
Flush & Reload Keys : Vide les caches clés/providers et recharge
Settings... : Affiche configuration actuelle
Current Configuration:
Store Name: MY
Store Location: CurrentUser
SmartCard Only: Yes
Log Level: 2
Edit registry to change:
HKLM\SOFTWARE\San@sro Inc\pkcs11-cng
Exit : Arrêt propre (signale g_shutdown_event)
- Fenêtre cachée avec pompe à messages
GetMessage/DispatchMessageloop- Événement
g_tray_ready_eventpour synchronisation - Cleanup automatique (
Shell_NotifyIcon(NIM_DELETE))
┌─────────────────────────────────────────────┐
│ WSL2 (Linux) │
│ - socat UNIX-LISTEN → TCP:127.0.0.1:10022 │
└─────────────────────────────────────────────┘
│
│ TCP
v
┌─────────────────────────────────────────────┐
│ Windows Host │
│ - ssh-agent.exe (listener 127.0.0.1:10022)│
│ - CNG/KSP → Smartcard │
└─────────────────────────────────────────────┘
Sécurité :
- Thread accepteur avec shutdown réactif
- Thread par client (max 16)
- Timeout connexion : 1 seconde
- Tableau statique
g_wsl2_clients[16] CRITICAL_SECTIONpar slot- Éviction LRU si pool plein
- Cleanup automatique au shutdown
Mode mirrored (Windows 11 22H2+) :
socat UNIX-LISTEN:"$SSH_AUTH_SOCK",fork,unlink-early \
TCP:127.0.0.1:10022 > /dev/null 2>&1 &Mode NAT classique :
# Récupérer l'IP du host Windows
HOST_IP=$(ip route | grep default | awk '{print $3}')
socat UNIX-LISTEN:"$SSH_AUTH_SOCK",fork,unlink-early \
TCP:$HOST_IP:10022 > /dev/null 2>&1 &BOOL wsl2_network_start(WORD port, HANDLE shutdown_event);
void wsl2_network_stop(void);
BOOL wsl2_network_is_running(void);
DWORD wsl2_network_get_client_count(void);typedef enum {
AGENT_NONE = 0,
AGENT_OPENSSH_NATIVE, // OpenSSH for Windows (ssh-agent.exe)
AGENT_PAGEANT, // PuTTY Pageant (fenêtre "Pageant")
AGENT_SRO_USERLAND, // SRO SSH-Agent userland
AGENT_SRO_SERVICE, // SRO SSH-Agent service Windows
AGENT_UNKNOWN // Agent inconnu détecté
} AGENT_TYPE;OpenSSH natif :
- Recherche processus
ssh-agent.exeviaCreateToolhelp32Snapshot - Vérifie que ce n'est pas le processus courant
Pageant :
- Recherche fenêtre
FindWindowW(L"Pageant", L"Pageant") - Vérifie que ce n'est pas la fenêtre du processus courant
SRO Userland :
- Tentative
CreateFileW(\\.\pipe\openssh-ssh-agent) - Si succès : un agent écoute déjà
SRO Service :
- Requête SCM
OpenServiceW(L"SROSSHAgentCNG") - Vérification statut
SERVICE_RUNNING
Affiché au démarrage si conflit détecté :
⚠ SSH Agent Conflict Detected
The following SSH agents are already running:
• OpenSSH Native (ssh-agent.exe)
• PuTTY Pageant
Running multiple agents may cause conflicts.
Do you want to continue anyway?
[Continue] [Stop conflicting agents] [Exit]
Actions :
- Continue : Lance quand même (risque de conflit)
- Stop : Tente d'arrêter les agents (si possible)
- Exit : Quitte sans lancer
BOOL detect_running_agents(AGENT_TYPE* detected_agents, DWORD* count);
BOOL show_agent_conflict_dialog(const AGENT_TYPE* agents, DWORD count);
const WCHAR* agent_type_to_string(AGENT_TYPE agent);Aucune. Le binaire est auto-contenu et ne charge que des DLL système :
kernel32.dll(toujours présent)advapi32.dll(registry, SCM)crypt32.dll(certificats)ncrypt.dll(CNG)bcrypt.dll(hashing)wtsapi32.dll(sessions)shell32.dll(tray icon)ws2_32.dll(Winsock)cryptui.dll(dialogue sélection certificat)
Pas de CRT. Toutes les opérations mémoire via RtlCopyMemory, RtlZeroMemory, RtlEqualMemory.
- Pas de chiffrement. SSH-agent n'implémente que la signature (pas
SSH2_AGENTC_*_ENCRYPT). - Pas de contraintes clés. Pas d'implémentation de
SSH2_AGENTC_ADD_ID_CONSTRAINED. - Cache PIN CNG. Géré par Windows/minidriver, pas de contrôle applicatif fin.
- Pool helpers limité. 16 helpers max en mode service (configurable via
MAX_HELPERS).
- PKCS#11 complet (14 mécanismes)
- SSH-agent multi-client
- Pageant compatible PuTTY
- WSL2 listener TCP
- Service stub Windows
- Tray icon avec statistiques
- Export clés publiques (OpenSSH + RFC4716)
- Détection conflits agents
- Cache clés/providers (4h timeout)
- Support EdDSA (Ed25519, Ed448)
- Support Brainpool
- UI graphique de configuration (Registry Editor simplifié)
- Métriques et monitoring (compteurs signature)
- Export configuration JSON
Les contributions sont les bienvenues ! Merci de :
- Respecter l'architecture souveraine (pas de CRT)
- Utiliser uniquement Win32 API
- Documenter les fonctions publiques
- Tester avec smartcards réelles
- Suivre le style de code existant
Ce logiciel est la propriété de San@sro inc.
Il est distribué selon un modèle de Licence de Confiance :
• Usage Personnel & Éducation : Gratuit et encouragé.
• Usage Professionnel / Commercial : Requiert l'achat d'une Licence de Paix Technique.
L'utilisation en entreprise sans licence valide constitue une violation des droits d'auteur,
malgré l'absence volontaire de tout verrou technique.
La redistribution est autorisée à condition que :
• le binaire reste intact,
• la signature Authenticode originale soit préservée.
Ce logiciel est fourni « tel quel », sans garantie d’aucune sorte.
La licence complète (FR + EN), incluant les définitions, conditions de redistribution, durée, résiliation et modalités d’obtention d’une Licence de Paix Technique, est disponible ici :
Pour toute demande de licence professionnelle :
📧 san@sro.ca
SRO PKCS11 – SSH Agent CNG ne manipule aucun secret sensible :
le PIN, les clés privées et les opérations cryptographiques sont entièrement gérés par Windows (CNG/KSP/minidriver).
Pour signaler un bug, un comportement anormal ou une vulnérabilité potentielle, une politique de divulgation responsable est disponible ici :
Contact sécurité :
📧 san@sro.ca
SRO PKCS11 – SSH Agent CNG
Souverain. Robuste. Opérationnel.
Un seul binaire pour tout faire.