diff --git a/contrib/win32/win32compat/ssh-agent/agent-request.h b/contrib/win32/win32compat/ssh-agent/agent-request.h index f0dcbb978b89..858360a22fcd 100644 --- a/contrib/win32/win32compat/ssh-agent/agent-request.h +++ b/contrib/win32/win32compat/ssh-agent/agent-request.h @@ -18,5 +18,7 @@ int process_request_identities(struct sshbuf*, struct sshbuf*, struct agent_conn int process_sign_request(struct sshbuf*, struct sshbuf*, struct agent_connection*); int process_remove_key(struct sshbuf*, struct sshbuf*, struct agent_connection*); int process_remove_all(struct sshbuf*, struct sshbuf*, struct agent_connection*); +int process_add_smartcard_key(struct sshbuf*, struct sshbuf*, struct agent_connection*); +int process_remove_smartcard_key(struct sshbuf*, struct sshbuf*, struct agent_connection*); /* auth */ diff --git a/contrib/win32/win32compat/ssh-agent/agent.h b/contrib/win32/win32compat/ssh-agent/agent.h index 2fb5e8236d67..6e8d8a8a14db 100644 --- a/contrib/win32/win32compat/ssh-agent/agent.h +++ b/contrib/win32/win32compat/ssh-agent/agent.h @@ -8,6 +8,8 @@ #define SSH_AGENT_ROOT SSH_REGISTRY_ROOT L"\\Agent" #define SSH_KEYS_KEY L"Keys" #define SSH_KEYS_ROOT SSH_AGENT_ROOT L"\\" SSH_KEYS_KEY +#define SSH_PKCS11_PROVIDERS_KEY L"PKCS11_Providers" +#define SSH_PKCS11_PROVIDERS_ROOT SSH_AGENT_ROOT L"\\" SSH_PKCS11_PROVIDERS_KEY #define HEADER_SIZE 4 diff --git a/contrib/win32/win32compat/ssh-agent/connection.c b/contrib/win32/win32compat/ssh-agent/connection.c index ad6aa668cdeb..1f569bc9ae95 100644 --- a/contrib/win32/win32compat/ssh-agent/connection.c +++ b/contrib/win32/win32compat/ssh-agent/connection.c @@ -28,9 +28,14 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "includes.h" #include "agent.h" #include "agent-request.h" +#ifdef ENABLE_PKCS11 +#include "ssh-pkcs11.h" +#endif + #pragma warning(push, 3) int process_request(struct agent_connection*); @@ -156,6 +161,15 @@ process_request(struct agent_connection* con) case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: r = process_remove_all(request, response, con); break; +#ifdef ENABLE_PKCS11 + case SSH_AGENTC_ADD_SMARTCARD_KEY: + case SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED: + r = process_add_smartcard_key(request, response, con); + break; + case SSH_AGENTC_REMOVE_SMARTCARD_KEY: + r = process_remove_smartcard_key(request, response, con); + break; +#endif /* ENABLE_PKCS11 */ default: debug("unknown agent request %d", type); r = -1; diff --git a/contrib/win32/win32compat/ssh-agent/keyagent-request.c b/contrib/win32/win32compat/ssh-agent/keyagent-request.c index 9d7dfce77aa6..5dbf26cb898a 100644 --- a/contrib/win32/win32compat/ssh-agent/keyagent-request.c +++ b/contrib/win32/win32compat/ssh-agent/keyagent-request.c @@ -208,30 +208,43 @@ static int sign_blob(const struct sshkey *pubkey, u_char ** sig, size_t *siglen, DWORD regdatalen = 0, keyblob_len = 0; struct sshbuf* tmpbuf = NULL; char *keyblob = NULL; +#ifdef ENABLE_PKCS11 + int is_pkcs11_key = 0; +#endif /* ENABLE_PKCS11 */ *sig = NULL; *siglen = 0; - if ((thumbprint = sshkey_fingerprint(pubkey, SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL || - get_user_root(con, &user_root) != 0 || - RegOpenKeyExW(user_root, SSH_KEYS_ROOT, - 0, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_WOW64_64KEY | KEY_ENUMERATE_SUB_KEYS, ®) != 0 || - RegOpenKeyExA(reg, thumbprint, 0, - STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_WOW64_64KEY, &sub) != 0 || - RegQueryValueExW(sub, NULL, 0, NULL, NULL, ®datalen) != ERROR_SUCCESS || - (regdata = malloc(regdatalen)) == NULL || - RegQueryValueExW(sub, NULL, 0, NULL, regdata, ®datalen) != ERROR_SUCCESS || - convert_blob(con, regdata, regdatalen, &keyblob, &keyblob_len, FALSE) != 0 || - (tmpbuf = sshbuf_from(keyblob, keyblob_len)) == NULL) - goto done; +#ifdef ENABLE_PKCS11 + if ((prikey = lookup_key(pubkey)) == NULL) { +#endif /* ENABLE_PKCS11 */ + + if ((thumbprint = sshkey_fingerprint(pubkey, SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL || + get_user_root(con, &user_root) != 0 || + RegOpenKeyExW(user_root, SSH_KEYS_ROOT, + 0, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_WOW64_64KEY | KEY_ENUMERATE_SUB_KEYS, ®) != 0 || + RegOpenKeyExA(reg, thumbprint, 0, + STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_WOW64_64KEY, &sub) != 0 || + RegQueryValueExW(sub, NULL, 0, NULL, NULL, ®datalen) != ERROR_SUCCESS || + (regdata = malloc(regdatalen)) == NULL || + RegQueryValueExW(sub, NULL, 0, NULL, regdata, ®datalen) != ERROR_SUCCESS || + convert_blob(con, regdata, regdatalen, &keyblob, &keyblob_len, FALSE) != 0 || + (tmpbuf = sshbuf_from(keyblob, keyblob_len)) == NULL) + sshkey_private_deserialize(tmpbuf, &prikey) != 0) + goto done; + +#ifdef ENABLE_PKCS11 + } + else + is_pkcs11_key = 1; +#endif /* ENABLE_PKCS11 */ if (flags & SSH_AGENT_RSA_SHA2_256) algo = "rsa-sha2-256"; else if (flags & SSH_AGENT_RSA_SHA2_512) algo = "rsa-sha2-512"; - if (sshkey_private_deserialize(tmpbuf, &prikey) != 0 || - sshkey_sign(prikey, sig, siglen, blob, blen, algo, NULL, NULL, 0) != 0) { + if (sshkey_sign(prikey, sig, siglen, blob, blen, algo, 0) != 0) { debug("cannot sign using retrieved key"); goto done; } @@ -245,8 +258,11 @@ static int sign_blob(const struct sshkey *pubkey, u_char ** sig, size_t *siglen, free(regdata); if (tmpbuf) sshbuf_free(tmpbuf); - if (prikey) - sshkey_free(prikey); +#ifdef ENABLE_PKCS11 + if (!is_pkcs11_key) +#endif /* ENABLE_PKCS11 */ + if (prikey) + sshkey_free(prikey); if (thumbprint) free(thumbprint); if (user_root) @@ -268,6 +284,66 @@ process_sign_request(struct sshbuf* request, struct sshbuf* response, struct age int r, request_invalid = 0, success = 0; struct sshkey *key = NULL; +#ifdef ENABLE_PKCS11 + int i, count = 0, index = 0;; + wchar_t sub_name[MAX_KEY_LENGTH]; + DWORD sub_name_len = MAX_KEY_LENGTH; + DWORD pin_len, epin_len, provider_len; + char *pin = NULL, *epin = NULL, *provider = NULL; + HKEY root = 0, sub = 0, user_root = 0; + struct sshkey **keys = NULL; + + pkcs11_init(0); + + if (get_user_root(con, &user_root) != 0 || + RegOpenKeyExW(user_root, SSH_PKCS11_PROVIDERS_ROOT, 0, STANDARD_RIGHTS_READ | KEY_ENUMERATE_SUB_KEYS | KEY_WOW64_64KEY, &root) != 0) { + success = 1; + goto done; + } + + while (1) { + sub_name_len = MAX_KEY_LENGTH; + if (sub) { + RegCloseKey(sub); + sub = NULL; + } + if (RegEnumKeyExW(root, index++, sub_name, &sub_name_len, NULL, NULL, NULL, NULL) == 0) { + if (RegOpenKeyExW(root, sub_name, 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &sub) == 0 && + RegQueryValueExW(sub, L"provider", 0, NULL, NULL, &provider_len) == 0 && + RegQueryValueExW(sub, L"pin", 0, NULL, NULL, &epin_len) == 0) { + if (provider) + free(provider); + if (pin) + free(pin); + if (epin) + free(epin); + provider = NULL; + pin = NULL; + epin = NULL; + + if ((epin = malloc(epin_len + 1)) == NULL || + (provider = malloc(provider_len + 1)) == NULL || + RegQueryValueExW(sub, L"provider", 0, NULL, provider, &provider_len) != 0 || + RegQueryValueExW(sub, L"pin", 0, NULL, epin, &epin_len) != 0) + goto done; + provider[provider_len] = '\0'; + epin[epin_len] = '\0'; + if (convert_blob(con, epin, epin_len, &pin, &pin_len, 0) != 0 || + (pin = realloc(pin, pin_len + 1)) == NULL) { + goto done; + } + pin[pin_len] = '\0'; + count = pkcs11_add_provider(provider, pin, &keys); + for (i = 0; i < count; i++) { + add_key(keys[i], provider); + } + } + } + else + break; + } +#endif /* ENABLE_PKCS11 */ + if (sshbuf_get_string_direct(request, &blob, &blen) != 0 || sshbuf_get_string_direct(request, &data, &dlen) != 0 || sshbuf_get_u32(request, &flags) != 0 || @@ -300,6 +376,22 @@ process_sign_request(struct sshbuf* request, struct sshbuf* response, struct age sshkey_free(key); if (signature) free(signature); +#ifdef ENABLE_PKCS11 + del_all_keys(); + pkcs11_terminate(); + if (provider) + free(provider); + if (pin) + free(pin); + if (epin) + free(epin); + if (user_root) + RegCloseKey(user_root); + if (root) + RegCloseKey(root); + if (sub) + RegCloseKey(sub); +#endif /* ENABLE_PKCS11 */ return r; } @@ -355,6 +447,7 @@ process_remove_all(struct sshbuf* request, struct sshbuf* response, struct agent } RegDeleteTreeW(root, SSH_KEYS_KEY); + RegDeleteTreeW(root, SSH_PKCS11_PROVIDERS_KEY); done: r = 0; if (sshbuf_put_u8(response, SSH_AGENT_SUCCESS) != 0) @@ -446,8 +539,174 @@ process_request_identities(struct sshbuf* request, struct sshbuf* response, stru return r; } +#ifdef ENABLE_PKCS11 +int process_add_smartcard_key(struct sshbuf* request, struct sshbuf* response, struct agent_connection* con) +{ + char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX]; + int i, count = 0, r = 0, request_invalid = 0, success = 0; + struct sshkey **keys = NULL; + struct sshkey* key = NULL; + size_t pubkey_blob_len, provider_len, pin_len, epin_len; + u_char *pubkey_blob = NULL; + char *thumbprint = NULL; + char *epin = NULL; + HKEY reg = 0, sub = 0, user_root = 0; + SECURITY_ATTRIBUTES sa = {0, NULL, 0}; + + if ((r = sshbuf_get_cstring(request, &provider, &provider_len)) != 0 || + (r = sshbuf_get_cstring(request, &pin, &pin_len)) != 0) { + debug("add smartcard request is invalid"); + request_invalid = 1; + goto done; + } + + if (realpath(provider, canonical_provider) == NULL) { + debug("failed PKCS#11 add of \"%.100s\": realpath: %s", + provider, strerror(errno)); + request_invalid = 1; + goto done; + } + + // Remove 'drive root' if exists + if (canonical_provider[0] == '/') + memmove(canonical_provider, canonical_provider + 1, strlen(canonical_provider)); + if (get_user_root(con, &user_root) != 0 || + is_reg_sub_key_exists(user_root, SSH_PKCS11_PROVIDERS_ROOT, canonical_provider)) + goto done; + + pkcs11_init(0); -int process_keyagent_request(struct sshbuf* request, struct sshbuf* response, struct agent_connection* con) + count = pkcs11_add_provider(canonical_provider, pin, &keys); + if (count <= 0) { + debug("failed to add key to store"); + goto done; + } + for (i = 0; i < count; i++) { + key = keys[i]; + memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES)); + sa.nLength = sizeof(sa); + if ((!ConvertStringSecurityDescriptorToSecurityDescriptorW(REG_KEY_SDDL, SDDL_REVISION_1, &sa.lpSecurityDescriptor, &sa.nLength)) || + sshkey_to_blob(key, &pubkey_blob, &pubkey_blob_len) != 0 || + ((thumbprint = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL) || + RegCreateKeyExW(user_root, SSH_KEYS_ROOT, 0, 0, 0, KEY_WRITE | KEY_WOW64_64KEY, &sa, ®, NULL) != 0 || + RegCreateKeyExA(reg, thumbprint, 0, 0, 0, KEY_WRITE | KEY_WOW64_64KEY, &sa, &sub, NULL) != 0 || + RegSetValueExW(sub, NULL, 0, REG_BINARY, pubkey_blob, (DWORD)pubkey_blob_len) != 0 || + RegSetValueExW(sub, L"pub", 0, REG_BINARY, pubkey_blob, (DWORD)pubkey_blob_len) != 0 || + RegSetValueExW(sub, L"type", 0, REG_DWORD, (BYTE*)&key->type, 4) != 0 || + RegSetValueExW(sub, L"comment", 0, REG_BINARY, canonical_provider, (DWORD)strlen(canonical_provider)) != 0) { + debug("failed to add key to store"); + goto done; + } + } + + debug("added smartcard keys to store"); + + memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES)); + sa.nLength = sizeof(sa); + if ((!ConvertStringSecurityDescriptorToSecurityDescriptorW(REG_KEY_SDDL, SDDL_REVISION_1, &sa.lpSecurityDescriptor, &sa.nLength)) || + convert_blob(con, pin, (DWORD)pin_len, &epin, (DWORD*)&epin_len, 1) != 0 || + get_user_root(con, &user_root) != 0 || + RegCreateKeyExW(user_root, SSH_PKCS11_PROVIDERS_ROOT, 0, 0, 0, KEY_WRITE | KEY_WOW64_64KEY, &sa, ®, NULL) != 0 || + RegCreateKeyExA(reg, canonical_provider, 0, 0, 0, KEY_WRITE | KEY_WOW64_64KEY, &sa, &sub, NULL) != 0 || + RegSetValueExW(sub, L"provider", 0, REG_BINARY, canonical_provider, (DWORD)strlen(canonical_provider)) != 0 || + RegSetValueExW(sub, L"pin", 0, REG_BINARY, epin, (DWORD)epin_len) != 0) { + debug("failed to add pkcs11 provider to store"); + goto done; + } + + debug("added pkcs11 provider to store"); + success = 1; +done: + r = 0; + if (request_invalid) + r = -1; + else if (sshbuf_put_u8(response, success ? SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE) != 0) + r = -1; + + /* delete created reg keys if not succeeded*/ + if ((success == 0) && reg) { + if (thumbprint) + RegDeleteKeyExA(reg, thumbprint, KEY_WOW64_64KEY, 0); + if (canonical_provider) + RegDeleteKeyExA(reg, canonical_provider, KEY_WOW64_64KEY, 0); + } + pkcs11_terminate(); + + if (sa.lpSecurityDescriptor) + LocalFree(sa.lpSecurityDescriptor); + for (i = 0; i < count; i++) + sshkey_free(keys[i]); + if (thumbprint) + free(thumbprint); + if (pubkey_blob) + free(pubkey_blob); + if (provider) + free(provider); + if (pin) + free(pin); + if (epin) + free(epin); + if (user_root) + RegCloseKey(user_root); + if (reg) + RegCloseKey(reg); + if (sub) + RegCloseKey(sub); + return r; +} + +int process_remove_smartcard_key(struct sshbuf* request, struct sshbuf* response, struct agent_connection* con) +{ + char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX]; + int r = 0, request_invalid = 0, success = 0, index = 0; + HKEY user_root = 0; + + if ((r = sshbuf_get_cstring(request, &provider, NULL)) != 0 || + (r = sshbuf_get_cstring(request, &pin, NULL)) != 0) { + debug("remove smartcard request is invalid"); + request_invalid = 1; + goto done; + } + + if (realpath(provider, canonical_provider) == NULL) { + debug("failed PKCS#11 add of \"%.100s\": realpath: %s", + provider, strerror(errno)); + request_invalid = 1; + goto done; + } + + // Remove 'drive root' if exists + if (canonical_provider[0] == '/') + memmove(canonical_provider, canonical_provider + 1, strlen(canonical_provider)); + + if (get_user_root(con, &user_root) != 0) { + success = 1; + goto done; + } + + if (remove_matching_subkeys_from_registry(user_root, SSH_KEYS_ROOT, L"comment", canonical_provider) != 0 || + remove_matching_subkeys_from_registry(user_root, SSH_PKCS11_PROVIDERS_ROOT, L"provider", canonical_provider) != 0) { + goto done; + } + + success = 1; +done: + r = 0; + if (request_invalid) + r = -1; + else if (sshbuf_put_u8(response, success ? SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE) != 0) + r = -1; + if (provider) + free(provider); + if (pin) + free(pin); + if (user_root) + RegCloseKey(user_root); + return r; +} +#endif /* ENABLE_PKCS11 */ + +int process_keyagent_request(struct sshbuf* request, struct sshbuf* response, struct agent_connection* con) { u_char type; @@ -466,10 +725,19 @@ int process_keyagent_request(struct sshbuf* request, struct sshbuf* response, st return process_remove_key(request, response, con); case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: return process_remove_all(request, response, con); +#ifdef ENABLE_PKCS11 + case SSH_AGENTC_ADD_SMARTCARD_KEY: + return process_add_smartcard_key(request, response, con); + case SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED: + return process_add_smartcard_key(request, response, con); + case SSH_AGENTC_REMOVE_SMARTCARD_KEY: + return process_remove_smartcard_key(request, response, con); + break; +#endif /* ENABLE_PKCS11 */ default: debug("unknown key agent request %d", type); - return -1; + return -1; } } -#pragma warning(pop) \ No newline at end of file +#pragma warning(pop) diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c index 844aa9fff740..552c722b6698 100644 --- a/ssh-pkcs11.c +++ b/ssh-pkcs11.c @@ -77,6 +77,13 @@ struct pkcs11_key { int keyid_len; }; +struct pkcs11_keyinfo { + TAILQ_ENTRY(pkcs11_keyinfo) next; + struct sshkey *key; + char *providername; +}; +TAILQ_HEAD(, pkcs11_keyinfo) pkcs11_keylist; + int pkcs11_interactive = 0; #ifdef HAVE_EC_KEY_METHOD_NEW @@ -96,6 +103,7 @@ pkcs11_init(int interactive) { pkcs11_interactive = interactive; TAILQ_INIT(&pkcs11_providers); + TAILQ_INIT(&pkcs11_keylist); return (0); } @@ -1853,6 +1861,47 @@ pkcs11_destroy_keypair(char *provider_id, char *pin, unsigned long slotidx, return (k); } + +void +add_key(struct sshkey *k, char *name) +{ + struct pkcs11_keyinfo *ki; + + ki = xcalloc(1, sizeof(*ki)); + ki->providername = xstrdup(name); + ki->key = k; + TAILQ_INSERT_TAIL(&pkcs11_keylist, ki, next); +} + +void +del_all_keys() +{ + struct pkcs11_keyinfo *ki, *nxt; + + for (ki = TAILQ_FIRST(&pkcs11_keylist); ki; ki = nxt) { + nxt = TAILQ_NEXT(ki, next); + TAILQ_REMOVE(&pkcs11_keylist, ki, next); + free(ki->providername); + sshkey_free(ki->key); + free(ki); + } +} + +/* lookup matching 'private' key */ +struct sshkey * +lookup_key(const struct sshkey *k) +{ + struct pkcs11_keyinfo *ki; + + TAILQ_FOREACH(ki, &pkcs11_keylist, next) { + debug("check %p %s", ki, ki->providername); + if (sshkey_equal(k, ki->key)) { + return (ki->key); + } + } + return (NULL); +} + #endif /* WITH_PKCS11_KEYGEN */ #else /* ENABLE_PKCS11 */ diff --git a/ssh-pkcs11.h b/ssh-pkcs11.h index 81f1d7c5d392..462a251321c9 100644 --- a/ssh-pkcs11.h +++ b/ssh-pkcs11.h @@ -35,6 +35,10 @@ struct sshkey * u_int32_t *); #endif +struct sshkey * lookup_key(const struct sshkey *); +void add_key(struct sshkey *, char *); +void del_all_keys(); + #if !defined(WITH_OPENSSL) && defined(ENABLE_PKCS11) #undef ENABLE_PKCS11 #endif