
SRO PKCS11 – SSH Agent CNG é um agente Windows soberano, ultraleve e zero dependência que unifica PKCS#11, SSH-agent, Pageant e CNG/Smartcard em um único binário robusto. Projetado para ambientes exigentes, ele oferece criptografia de hardware nativa, isolamento service/userland, suporte completo para smartcards.
Unificação soberana PKCS#11 + SSH-agent + Pageant + CNG/Smartcard
Um executável Windows único que unifica quatro funções tradicionalmente separadas:
Soberana. Nenhuma dependência do CRT. Todas as operações de memória passam por RtlCopyMemory, RtlZeroMemory, RtlEqualMemory (FreeCRT.h). Unicode em todo lado (Win32 nativo). Nenhum malloc, memcpy, strlen, printf.
Segura. As chaves privadas nunca são exportadas. Nenhum PIN transita. CNG/KSP gere a UI PIN nativa do Windows. Isolamento estrito serviço ↔ userland através de pipes seguros.
Minimalista. Um único binário. Nenhuma DLL externa. Nenhum inchaço de registry. Instalação simples (regsvr32 ou -install).
Versátil. Suporte simultâneo de PKCS#11, SSH-agent, Pageant e WSL2 no mesmo processo.
┌──────────────────────────────────────────────────────────────┐ │ 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) │ └──────────────────────────────────────────────────────────────┘
## Modos de execução
### 1. Modo PKCS#11 (automático)
Carregado por:
- `ssh -I ssh-agent.exe user@host`
- Firefox (Security Devices → Load PKCS#11 Module)
- `pkcs11-tool --module ssh-agent.exe --list-objects`
Expõe as exportações PKCS#11 padrão:
- `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`
**Mecanismos suportados (14 no total):**
- `CKM_RSA_PKCS` (raw com padding)
- `CKM_RSA_X_509` (raw sem 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. Modo agente userland (standalone)```bash
ssh-agent.exe
\\.\pipe\openssh-ssh-agent em sessão do usuárioCompatível com:
set SSH_AUTH_SOCK=\\.\pipe\openssh-ssh-agent)ssh-agent.exe -install net start SROSSHAgentCNG
- Roda na sessão 0 (SYSTEM)
- Aceita conexões no pipe global
- Cria um pipe interno por cliente (protegido por SID)
- Inicia um helper userland com `CreateProcessAsUserW`
- Encaminha as mensagens sem tocar nos segredos
- Pool de helpers com timeout de 4h (reutilização automática)
- Evicção LRU se pool cheio
**Vantagens:**
- UI PIN na sessão do usuário (não na sessão 0)
- Compatível com ambientes endurecidos
- Isolamento estrito serviço ↔ crypto
- Multiplexação multi-usuários
### 4. Modo helper crypto userland```bash
ssh-agent.exe -useragent -pipe \\.\pipe\ssh-ksp-helper-{GUID}
Iniciado automaticamente pelo serviço :
NCryptSignHash (UI PIN nativa)regsvr32 ssh-agent.exe
Crie as chaves :
- `HKLM\SOFTWARE\San@sro Inc\PKCS11-SSH-Agent`
- `HKCU\SOFTWARE\San@sro Inc\PKCS11-SSH-Agent`
- `HKCU\SOFTWARE\Mozilla\Firefox\PKCS11Modules\SROSSHAgent`
### Instalar o serviço Windows```bash
ssh-agent.exe -install
net start SROSSHAgentCNG
Adicionar ao ~/.bashrc ou ~/.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
### Desinstalar```bash
regsvr32 /u ssh-agent.exe
ssh-agent.exe -remove
Chave: HKLM\SOFTWARE\San@sro Inc\pkcs11-cng ou HKCU\SOFTWARE\San@sro Inc\pkcs11-cng
Exemplo:``` StoreName = "MY" StoreLocation = "CurrentUser" SmartCardOnly = 1 AllowedKSP = "Microsoft Smart Card Key Storage Provider;YubiKey Smart Card Key Storage Provider" RelaxCheckMode = 0 LogLevel = 2
---
## Protocolos suportados
### SSH-Agent
#### SSH2_AGENTC_REQUEST_IDENTITIES (11)
Requisição :```
[type=11]
Resposta :``` [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]
Solicitação :``` [type=13][len][key_blob][len][data][flags]
**Flags :**
- `0x00` : ssh-rsa (SHA-1, legacy)
- `0x02` : rsa-sha2-256
- `0x04` : rsa-sha2-512
Resposta :```
[type=14][len][signature_blob]
signature_blob :``` [len]["rsa-sha2-256"][len][signature_data]
### Pageant
PuTTY compatível via `WM_COPYDATA` :
1. Cliente cria uma memória compartilhada via `CreateFileMapping`
2. Escreve a requisição SSH-agent no formato padrão
3. Envia `WM_COPYDATA` para a janela "Pageant"
4. Lê a resposta na memória compartilhada
Formato da memória compartilhada:```
[uint32 length][SSH-agent payload]
Listener TCP em 127.0.0.1:10022:
handle_ssh_message()O Windows gerencia inteiramente o PIN via CNG/KSP e o minidriver do smartcard.
O módulo nunca armazena o PIN e nunca o vê transitar:
Cache PIN: Gerenciado automaticamente pelo Windows/minidriver (não precisa de cache de aplicativo).
Flags NCrypt:
NCRYPT_SILENT_FLAG (sem UI)SILENT_FLAG falhar: Retry automático com UICache de chaves (timeout 4h):
CNG_KEY_INFO (handle, provider, container)Cache de providers (timeout 4h):
NCRYPT_PROV_HANDLENCryptOpenStorageProvidercng_store_enum_certificates(cfg, callback, user_data);
Filtro :
- Chaves privadas disponíveis
- KSP autorizados (se `SmartCardOnly`)
- Chaves não exportáveis (se `SmartCardOnly`)
### Assinatura```c
cng_sign_hash(key_info, mechanism, hash, hash_len, signature, &sig_len);
Mecanismo → Padding :
CKM_RSA_PKCS → BCRYPT_PAD_PKCS1CKM_SHA256_RSA_PKCS → BCRYPT_PAD_PKCS1 + BCRYPT_SHA256_ALGORITHMCKM_SHA256_RSA_PKCS_PSS → BCRYPT_PAD_PSS + tamanho do salt = tamanho do hashCKM_ECDSA_SHA256 → Sem padding (assinatura bruta)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
Curvas suportadas :
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)Suporte autenticação Active Directory :```c cng_extract_upn_from_certificate(cert, upn, upn_size);
Extrait l'extension `szOID_NT_PRINCIPAL_NAME` pour l'utiliser comme commentaire SSH.
---
## Segurança
### Chaves privadas
**Nunca exportadas.** Todas as operações criptográficas são delegadas ao CNG/KSP. `NCryptSignHash` é chamado com o handle da chave, nunca com a chave em si.
### PIN
**Gerenciado exclusivamente pelo Windows (CNG/KSP/minidriver).**
O módulo **nunca armazena o PIN** e **nunca o vê transitar** :
- O PIN nunca é transmitido ao módulo PKCS#11
- A UI do PIN é exibida pelo minidriver do smartcard
- O cache do PIN é gerenciado automaticamente pelo Windows/minidriver
- Em modo de serviço: o helper userland (sessão interativa) recebe a UI do PIN
**Modo de serviço (passthrough puro):**
O serviço stub faz APENAS forwarding transparente:
- Cliente → Serviço → Helper (encaminha mensagem SSH-agent)
- Helper → Serviço → Cliente (encaminha resposta SSH-agent)
- O serviço nunca analisa o conteúdo
- O serviço nunca vê: PIN, hash, assinatura, chave
### Isolamento serviço ↔ userland
**Pipes seguros.** Cada pipe interno é:
- Gerado com um GUID único
- Criado com `FILE_FLAG_FIRST_PIPE_INSTANCE`
- DACL permitindo apenas o usuário atual
O helper userland invoca CNG/KSP na sessão interativa → UI PIN nativa.
### Auditoria
**Logs Unicode.** Todos os eventos são registrados via `utils_log()`:
- Conexões cliente
- Enumeração de chaves
- Requisições de assinatura
- Erros CNG/KSP
- Conflitos de agentes
**Localização:** OutputDebugString + arquivo opcional (`utils_set_log_file()`).
---
## Compatibilidade
| Ambiente | Modo | Status |
|------------------------------|------------------------|--------|
| OpenSSH for Windows | Standalone / Serviço | ✓ |
| Git for Windows | Standalone / Serviço | ✓ |
| Visual Studio | Standalone / Serviço | ✓ |
| WSL (npiperelay) | Standalone / Serviço | ✓ |
| WSL2 (TCP) | Standalone / Serviço | ✓ |
| PuTTY / plink / pscp | Pageant | ✓ |
| Firefox | PKCS#11 | ✓ |
| OpenSC / pkcs11-tool | PKCS#11 | ✓ |
| ssh -I (OpenSSH) | PKCS#11 | ✓ |
| Ambientes endurecidos | Service stub | ✓ |
| SmartCard GIDS | CNG/KSP | ✓ |
| SmartCard PIV | CNG/KSP | ✓ |
| YubiKey | CNG/KSP | ✓ |
| Nitrokey | CNG/KSP | ✓ |
---
## Exportação de chaves públicas
### Comando CLI```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 ----
Ordem de prioridade para o comentário:
TRAY_MODE_USERLAND (verde) :
TRAY_MODE_SERVICE (azul) :
Tooltip dinâmico:``` SRO SSH-Agent (Userland) 12 keys, 3 clients
Atualização:
- A cada 5 segundos
- A cada conexão/desconexão de cliente
- Ao flush do cache
### Menu de contexto
**Show Keys...** : Diálogo listando todas as chaves disponíveis```
═══════════════════════════════════════════════
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... : Abre o diálogo de seleção e copia para a área de transferência
Flush & Reload Keys : Limpa os caches de chaves/providers e recarrega
Settings... : Exibe a configuração atual``` 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** : Paragem limpa (sinaliza `g_shutdown_event`)
### Thread UI dedicada
- Janela oculta com bomba de mensagens
- Loop `GetMessage/DispatchMessage`
- Evento `g_tray_ready_event` para sincronização
- Limpeza automática (`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 │
└─────────────────────────────────────────────┘
Segurança:
g_wsl2_clients[16]CRITICAL_SECTION por slotModo espelhado (Windows 11 22H2+):```bash
socat UNIX-LISTEN:"$SSH_AUTH_SOCK",fork,unlink-early
TCP:127.0.0.1:10022 > /dev/null 2>&1 &
**Modo NAT clássico :**```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);
---
## Detecção de conflitos
### Agentes detectados```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;
OpenSSH nativo :
ssh-agent.exe via CreateToolhelp32SnapshotPageant :
FindWindowW(L"Pageant", L"Pageant")SRO Userland :
CreateFileW(\\.\pipe\openssh-ssh-agent)SRO Service :
OpenServiceW(L"SROSSHAgentCNG")SERVICE_RUNNINGExibido na inicialização se conflito detectado:``` ⚠ 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** : Lança mesmo assim (risco de conflito)
- **Stop** : Tenta parar os agentes (se possível)
- **Exit** : Sai sem lançar
### Função pública```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);
Nenhuma. O binário é autocontido e carrega apenas DLLs do sistema:
kernel32.dll (sempre presente)advapi32.dll (registro, SCM)crypt32.dll (certificados)ncrypt.dll (CNG)bcrypt.dll (hash)wtsapi32.dll (sessões)shell32.dll (ícone da bandeja)ws2_32.dll (Winsock)cryptui.dll (diálogo de seleção de certificado)Sem CRT. Todas as operações de memória via RtlCopyMemory, RtlZeroMemory, RtlEqualMemory.
SSH2_AGENTC_*_ENCRYPT).SSH2_AGENTC_ADD_ID_CONSTRAINED.MAX_HELPERS).RelaxCheckMode = 1 para usá-las.As contribuições são bem-vindas! Por favor:
Este software é propriedade da San@sro inc.
É distribuído sob um modelo de Licença de Confiança:
• Uso Pessoal & Educacional: Gratuito e incentivado.
• Uso Profissional / Comercial: Requer a compra de uma Licença de Paz Técnica.
O uso em empresa sem licença válida constitui violação de direitos autorais,
apesar da ausência voluntária de qualquer trava técnica.
A redistribuição é autorizada desde que:
• o binário permaneça intacto,
• a assinatura Authenticode original seja preservada.
Este software é fornecido "como está", sem garantia de qualquer tipo.
A licença completa (FR + EN), incluindo definições, condições de redistribuição, duração, rescisão e modalidades de obtenção de uma Licença de Paz Técnica, está disponível aqui:
Para qualquer solicitação de licença profissional:
📧 [email protected]
SRO PKCS11 – SSH Agent CNG não manipula nenhum segredo sensível:
o PIN, as chaves privadas e as operações criptográficas são inteiramente gerenciados pelo Windows (CNG/KSP/minidriver).
Para relatar um bug, comportamento anormal ou vulnerabilidade potencial, uma política de divulgação responsável está disponível aqui:
Contato de segurança:
📧 [email protected]
SRO PKCS11 – SSH Agent CNG
Soberano. Robusto. Operacional.
Um único binário para tudo.
| Valor | Tipo | Descrição |
|---|
StoreName | REG_SZ | "MY", "Root", etc. (padrão: "MY") |
StoreLocation | REG_SZ | "CurrentUser" ou "LocalMachine" |
Mode | REG_SZ | "All" ou "SmartCard" |
SmartCardOnly | REG_DWORD | 1 = filtrar apenas smartcards |
AllowedKSP | REG_SZ | Lista de KSP autorizados (separados por ";") |
RelaxCheckMode | REG_DWORD | 1 = desativar validação EKU/KeyUsage/datas (YubiKey PIV auto-assinado) |
LogLevel | REG_DWORD | 0=off, 1=error, 2=info, 3=debug |