
SRO PKCS11 – SSH Agent CNG est un agent Windows souverain, ultra‑léger et zéro‑dépendance qui unifie PKCS#11, SSH-agent, Pageant et CNG/Smartcard dans un seul binaire robuste. Pensé pour les environnements exigeants, il offre une cryptographie matérielle native, une isolation service/userland, un support complet smartcards.
Sovereign unification PKCS#11 + SSH-agent + Pageant + CNG/Smartcard
A single Windows executable that unifies four traditionally separate functions:
Sovereign. No CRT dependency. All memory operations use RtlCopyMemory, RtlZeroMemory, RtlEqualMemory (FreeCRT.h). Unicode everywhere (native Win32). No malloc, memcpy, strlen, printf.
Secure. Private keys are never exported. No PIN transits. CNG/KSP handles native Windows PIN UI. Strict service ↔ userland isolation via secure pipes.
Minimalist. Single binary. No external DLLs. No registry bloat. Simple installation (regsvr32 or -install).
Versatile. Simultaneous support for PKCS#11, SSH-agent, Pageant, and WSL2 in the same process.
┌──────────────────────────────────────────────────────────────┐ │ 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) │ └──────────────────────────────────────────────────────────────┘
---
## Execution Modes
### 1. PKCS#11 Mode (automatic)
Loaded by:
- `ssh -I ssh-agent.exe user@host`
- Firefox (Security Devices → Load PKCS#11 Module)
- `pkcs11-tool --module ssh-agent.exe --list-objects`
Exposes standard PKCS#11 exports:
- `C_Initialize`, `C_Finalize`, `C_GetInfo`
- `C_GetSlotList`, `C_GetSlotInfo`, `C_GetTokenInfo`
- `C_GetMechanismList`, `C_GetMechanismInfo`
- `C_OpenSession`, `C_CloseSession`, `C_Login`, `C_Logout`
- `C_FindObjectsInit`, `C_FindObjects`, `C_FindObjectsFinal`
- `C_GetAttributeValue`
- `C_SignInit`, `C_Sign`
- `C_VerifyInit`, `C_Verify`
- `C_DecryptInit`, `C_Decrypt`
- `C_GenerateRandom`, `C_SeedRandom`
**Supported mechanisms (14 total):**
- `CKM_RSA_PKCS` (raw with padding)
- `CKM_RSA_X_509` (raw without 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_SHA384`
- `CKM_ECDSA_SHA512`
### 2. Userland Agent Mode (standalone)```bash
ssh-agent.exe
\\.\pipe\openssh-ssh-agent in user sessionCompatible with:
set SSH_AUTH_SOCK=\\.\pipe\openssh-ssh-agent)ssh-agent.exe -install net start SROSSHAgentCNG
- Runs in session 0 (SYSTEM)
- Accepts connections on global pipe
- Creates an internal pipe per client (secured by SID)
- Launches a userland helper with `CreateProcessAsUserW`
- Forwards messages without touching secrets
- Helper pool with 4h timeout (automatic reuse)
- LRU eviction if pool full
**Advantages:**
- UI PIN in user session (not in session 0)
- Compatible with hardened environments
- Strict isolation service ↔ crypto
- Multi-user multiplexing
### 4. Userland crypto helper mode```bash
ssh-agent.exe -useragent -pipe \\.\pipe\ssh-ksp-helper-{GUID}
Launched automatically by the service:
NCryptSignHash (native PIN UI)regsvr32 ssh-agent.exe
Create the keys:
- `HKLM\SOFTWARE\San@sro Inc\PKCS11-SSH-Agent`
- `HKCU\SOFTWARE\San@sro Inc\PKCS11-SSH-Agent`
- `HKCU\SOFTWARE\Mozilla\Firefox\PKCS11Modules\SROSSHAgent`
### Install the Windows service```bash
ssh-agent.exe -install
net start SROSSHAgentCNG
Add to ~/.bashrc or ~/.zshrc :```bash
export SSH_AUTH_SOCK="$HOME/.ssh/agent.sock"
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 &
fi
### Uninstall```bash
regsvr32 /u ssh-agent.exe
ssh-agent.exe -remove
Key: HKLM\SOFTWARE\San@sro Inc\pkcs11-cng or HKCU\SOFTWARE\San@sro Inc\pkcs11-cng
Example:``` StoreName = "MY" StoreLocation = "CurrentUser" SmartCardOnly = 1 AllowedKSP = "Microsoft Smart Card Key Storage Provider;YubiKey Smart Card Key Storage Provider" RelaxCheckMode = 0 LogLevel = 2
---
## Supported protocols
### SSH-Agent
#### SSH2_AGENTC_REQUEST_IDENTITIES (11)
Request:```
[type=11]
Response :``` [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]
Request:``` [type=13][len][key_blob][len][data][flags]
**Flags :**
- `0x00` : ssh-rsa (SHA-1, legacy)
- `0x02` : rsa-sha2-256
- `0x04` : rsa-sha2-512
Response :```
[type=14][len][signature_blob]
signature_blob :``` [len]["rsa-sha2-256"][len][signature_data]
### Pageant
Compatible PuTTY via `WM_COPYDATA` :
1. Client creates shared memory via `CreateFileMapping`
2. Writes the SSH-agent request in standard format
3. Sends `WM_COPYDATA` to the "Pageant" window
4. Reads the response from the shared memory
Shared memory format :```
[uint32 length][SSH-agent payload]
TCP listener on 127.0.0.1:10022 :
handle_ssh_message()Windows fully manages the PIN via CNG/KSP and the smartcard minidriver.
The module never stores the PIN and never sees it in transit:
PIN Cache: Managed automatically by Windows/minidriver (no need for application cache).
NCrypt Flags:
NCRYPT_SILENT_FLAG (no UI)SILENT_FLAG fails: Automatic retry with UIKey cache (timeout 4h) :
CNG_KEY_INFO (handle, provider, container)Provider cache (timeout 4h) :
NCRYPT_PROV_HANDLENCryptOpenStorageProvidercng_store_enum_certificates(cfg, callback, user_data);
Filter:
- Private keys available
- Authorized KSPs (if `SmartCardOnly`)
- Non-exportable keys (if `SmartCardOnly`)
### Signature```c
cng_sign_hash(key_info, mechanism, hash, hash_len, signature, &sig_len);
Mechanism → 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 → No padding (raw signature)RSA :```c cng_cert_get_public_key(cert, modulus, &mod_len, exponent, &exp_len);
**ECDSA :**```c
cng_cert_get_ec_params(cert, params, ¶ms_len); // OID courbe
cng_cert_get_ec_point(cert, point, &point_len); // Point public
Supported curves:
nistp256 (OID: 1.2.840.10045.3.1.7), nistp384 (1.3.132.0.34), nistp521 (1.3.132.0.35)brainpoolP256r1, brainpoolP384r1, brainpoolP512r1ed25519 (OID: 1.3.101.112), ed448 (1.3.101.113)Support Active Directory authentication:```c cng_extract_upn_from_certificate(cert, upn, upn_size);
Extracts the `szOID_NT_PRINCIPAL_NAME` extension to use it as an SSH comment.
---
## Security
### Private Keys
**Never exported.** All cryptographic operations are delegated to CNG/KSP. `NCryptSignHash` is called with the key handle, never with the key itself.
### PIN
**Managed exclusively by Windows (CNG/KSP/minidriver).**
The module **never stores the PIN** and **never sees it in transit**:
- The PIN is never transmitted to the PKCS#11 module
- The PIN UI is displayed by the smartcard minidriver
- PIN caching is managed automatically by Windows/minidriver
- In service mode: the userland helper (interactive session) receives the PIN UI
**Service mode (pure passthrough):**
The service stub only does transparent forwarding:
- Client → Service → Helper (forward message SSH-agent)
- Helper → Service → Client (forward SSH-agent response)
- The service never parses the content
- The service never sees: PIN, hash, signature, key
### Service ↔ userland isolation
**Secure pipes.** Each internal pipe is:
- Generated with a unique GUID
- Created with `FILE_FLAG_FIRST_PIPE_INSTANCE`
- DACL allowing only the current user
The userland helper invokes CNG/KSP in the interactive session → native PIN UI.
### Audit
**Unicode logs.** All events are logged via `utils_log()`:
- Client connections
- Key enumeration
- Signature requests
- CNG/KSP errors
- Agent conflicts
**Location:** OutputDebugString + optional file (`utils_set_log_file()`).
---
## Compatibility
| Environment | 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 | ✓ |
| Hardened environments | Service stub | ✓ |
| SmartCard GIDS | CNG/KSP | ✓ |
| SmartCard PIV | CNG/KSP | ✓ |
| YubiKey | CNG/KSP | ✓ |
| Nitrokey | CNG/KSP | ✓ |
---
## Exporting public keys
### CLI command```bash
ssh-agent.exe -exportkey [output.pub]
CryptUIDlgSelectCertificateFromStoreOpenSSH :``` ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5... [email protected]
**RFC4716 :**```
---- BEGIN SSH2 PUBLIC KEY ----
Comment: "[email protected]"
AAAAB3NzaC1yc2EAAAADAQABAAABAQC5ABCDEF...
---- END SSH2 PUBLIC KEY ----
Priority order for the comment:
TRAY_MODE_USERLAND (green) :
TRAY_MODE_SERVICE (blue) :
Dynamic tooltip :``` SRO SSH-Agent (Userland) 12 keys, 3 clients
Mise à jour :
- Every 5 seconds
- On each client connection/disconnection
- On cache flush
### Context menu
**Show Keys...**: Dialog listing all available keys```
═══════════════════════════════════════════════
SRO SSH-Agent - Available Keys
═══════════════════════════════════════════════
[01] RSA-2048 - [email protected]
[02] ECDSA-nistp256 - [email protected]
[03] EdDSA-Ed25519 - [email protected]
═══════════════════════════════════════════════
Total: 3 keys
💡 Tip: Use 'Export Public Key' to copy SSH format
Export Public Key... : Opens the selection dialog and copies to clipboard
Flush & Reload Keys : Clears key/provider caches and reloads
Settings... : Displays current configuration``` Current Configuration:
Store Name: MY Store Location: CurrentUser SmartCard Only: Yes Relax Key Usage Check Mode: No Log Level: 2
Edit registry to change: HKLM\SOFTWARE\San@sro Inc\pkcs11-cng
**Exit** : Clean shutdown (signals `g_shutdown_event`)
### Dedicated UI Thread
- Hidden window with message pump
- `GetMessage/DispatchMessage` loop
- Event `g_tray_ready_event` for synchronization
- Automatic cleanup (`Shell_NotifyIcon(NIM_DELETE)`)
---
## WSL2 Support
### Architecture```
┌─────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────┘
Security:
g_wsl2_clients[16]CRITICAL_SECTION per slotMirrored mode (Windows 11 22H2+):```bash
socat UNIX-LISTEN:"$SSH_AUTH_SOCK",fork,unlink-early
TCP:127.0.0.1:10022 > /dev/null 2>&1 &
**Classic NAT mode:**```bash
# 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);
---
## Conflict detection
### Detected agents```c
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;
Native OpenSSH:
ssh-agent.exe process via CreateToolhelp32SnapshotPageant:
FindWindowW(L"Pageant", L"Pageant")SRO Userland:
CreateFileW(\\.\pipe\openssh-ssh-agent)SRO Service:
OpenServiceW(L"SROSSHAgentCNG")SERVICE_RUNNINGDisplayed at startup if conflict detected:``` ⚠ 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** : Run anyway (risk of conflict)
- **Stop** : Attempt to stop agents (if possible)
- **Exit** : Exit without running
### Public function```c
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);
None. The binary is self-contained and only loads system DLLs:
kernel32.dll (always present)advapi32.dll (registry, SCM)crypt32.dll (certificates)ncrypt.dll (CNG)bcrypt.dll (hashing)wtsapi32.dll (sessions)shell32.dll (tray icon)ws2_32.dll (Winsock)cryptui.dll (certificate selection dialog)No CRT. All memory operations via RtlCopyMemory, RtlZeroMemory, RtlEqualMemory.
SSH2_AGENTC_*_ENCRYPT).SSH2_AGENTC_ADD_ID_CONSTRAINED.MAX_HELPERS).RelaxCheckMode = 1 to use them.Contributions are welcome! Please:
This software is the property of San@sro inc.
It is distributed under a Trust License model:
• Personal & Educational Use: Free and encouraged.
• Professional / Commercial Use: Requires purchase of a Technical Peace License.
Use in a company without a valid license constitutes copyright infringement,
despite the deliberate absence of any technical lock.
Redistribution is permitted provided that: • the binary remains intact, • the original Authenticode signature is preserved.
This software is provided "as is", without warranty of any kind.
The complete license (FR + EN), including definitions, redistribution conditions, duration, termination, and how to obtain a Technical Peace License, is available here:
For any professional license request:
📧 [email protected]
SRO PKCS11 – SSH Agent CNG does not handle any sensitive secrets:
the PIN, private keys, and cryptographic operations are fully managed by Windows (CNG/KSP/minidriver).
To report a bug, abnormal behavior, or potential vulnerability, a responsible disclosure policy is available here:
Security contact:
📧 [email protected]
SRO PKCS11 – SSH Agent CNG
Sovereign. Robust. Operational.
A single binary to do it all.
| Value | Type | Description |
|---|
StoreName | REG_SZ | "MY", "Root", etc. (default: "MY") |
StoreLocation | REG_SZ | "CurrentUser" or "LocalMachine" |
Mode | REG_SZ | "All" or "SmartCard" |
SmartCardOnly | REG_DWORD | 1 = filter only smartcards |
AllowedKSP | REG_SZ | List of allowed KSPs (separated by ";") |
RelaxCheckMode | REG_DWORD | 1 = disable EKU/KeyUsage/date validation (YubiKey PIV self-signed) |
LogLevel | REG_DWORD | 0=off, 1=error, 2=info, 3=debug |