mirror of
				https://github.com/PowerShell/Win32-OpenSSH.git
				synced 2025-10-31 11:44:38 +01:00 
			
		
		
		
	Implementation of a generic wrap interface for bignum and diffie-hellman based upon Damien's wrap code in openssh-openbsd. This commit adds the generic interface along with the backing code for openssl, cng, and cng with an openssl fallback. Currently, openssl is the only provider for bignum and the diffie-hellman generic interface is only for static and negotiated oakley groups..
		
			
				
	
	
		
			488 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			488 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| 
 | |
| #include <includes.h>
 | |
| 
 | |
| 
 | |
| #include <sys/types.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <limits.h>
 | |
| 
 | |
| #include <openssl/dh.h>
 | |
| #include <openssl/evp.h>
 | |
| #include <openssl/bn.h>
 | |
| 
 | |
| #include "sshbuf.h"
 | |
| #include "packet.h"
 | |
| #include "ssherr.h"
 | |
| #include <bcrypt.h>
 | |
| #include "crypto-wrap.h"
 | |
| #include <VersionHelpers.h>
 | |
| 
 | |
| 
 | |
| // CNG Diffie-hellman Kex context 
 | |
| typedef struct cng_dh_ctx {
 | |
| 	int size;
 | |
| 	PBYTE g;
 | |
| 	PBYTE p;
 | |
| 	PBYTE pub;
 | |
| 	BCRYPT_KEY_HANDLE     hPrivate;
 | |
| 	BCRYPT_ALG_HANDLE     hAlg;
 | |
| } CNG_DH_CTX;
 | |
| 
 | |
| 
 | |
| struct sshdh {
 | |
| 	void *dh;
 | |
| };
 | |
| 
 | |
| struct sshbn {
 | |
| 	BIGNUM *bn;
 | |
| };
 | |
| 
 | |
| 
 | |
| int cng_supported(void)
 | |
| {
 | |
| 	return (IsWindows8Point1OrGreater());
 | |
| }
 | |
| 
 | |
| //function to reverse the order of a byte array
 | |
| static void
 | |
| cng_dh_swap_bytes(void *pv, size_t n)
 | |
| {
 | |
| 	char *p = (char*)pv;
 | |
| 	size_t lo, hi;
 | |
| 	for (lo = 0, hi = n - 1; hi>lo; lo++, hi--)
 | |
| 	{
 | |
| 		char tmp = p[lo];
 | |
| 		p[lo] = p[hi];
 | |
| 		p[hi] = tmp;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| static struct sshbn *
 | |
| bnwrap(BIGNUM *bn)
 | |
| {
 | |
| 	struct sshbn *ret;
 | |
| 
 | |
| 	if (bn == NULL)
 | |
| 		return NULL;
 | |
| 
 | |
| 	if ((ret = calloc(1, sizeof(*ret))) == NULL)
 | |
| 		return NULL;
 | |
| 	if ((ret->bn = BN_dup(bn)) == NULL) {
 | |
| 		free(ret);
 | |
| 		return NULL;
 | |
| 	}
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* DH wrappers */
 | |
| 
 | |
| struct sshdh *
 | |
| 	sshdh_new(void)
 | |
| {
 | |
| 	if (!cng_supported())
 | |
| 	{
 | |
| 		struct sshdh *ret;
 | |
| 
 | |
| 		if ((ret = calloc(1, sizeof(*ret))) == NULL)
 | |
| 			return NULL;
 | |
| 		if ((ret->dh = DH_new()) == NULL) {
 | |
| 			free(ret);
 | |
| 			return NULL;
 | |
| 		}
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return (struct sshdh *)(malloc(sizeof(struct sshdh)));
 | |
| }
 | |
| 
 | |
| void
 | |
| sshdh_free(struct sshdh *dh)
 | |
| {
 | |
| 	if (!cng_supported())
 | |
| 	{
 | |
| 		if (dh != NULL) {
 | |
| 			if (dh->dh != NULL)
 | |
| 				DH_free(dh->dh);
 | |
| 			explicit_bzero(dh, sizeof(*dh));
 | |
| 			free(dh);
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (dh != NULL) {
 | |
| 		if (dh->dh != NULL)
 | |
| 		{
 | |
| 			CNG_DH_CTX * pCtx = (CNG_DH_CTX*)dh->dh;
 | |
| 
 | |
| 			if (pCtx->hAlg)
 | |
| 				BCryptCloseAlgorithmProvider(pCtx->hAlg, 0);
 | |
| 
 | |
| 			if (pCtx->hPrivate)
 | |
| 				BCryptDestroyKey(pCtx->hPrivate);
 | |
| 
 | |
| 			ZeroMemory(pCtx, sizeof(*pCtx));
 | |
| 			free(pCtx);
 | |
| 		}
 | |
| 		free(dh);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| struct sshbn *
 | |
| 	sshdh_pubkey(struct sshdh *dh)
 | |
| {
 | |
| 	if (!cng_supported())
 | |
| 	{
 | |
| 		return bnwrap(((DH *)(dh->dh))->pub_key);
 | |
| 	}
 | |
| 
 | |
| 	CNG_DH_CTX				*pCtx = (CNG_DH_CTX*)dh->dh;
 | |
| 	struct sshbn * bn = NULL;
 | |
| 
 | |
| 	sshbn_from(pCtx->pub, pCtx->size, &bn);
 | |
| 	return bn;
 | |
| 
 | |
| }
 | |
| 
 | |
| struct sshbn *
 | |
| 	sshdh_p(struct sshdh *dh)
 | |
| {
 | |
| 	if (!cng_supported())
 | |
| 	{
 | |
| 		return bnwrap(((DH *)(dh->dh))->p);
 | |
| 	}
 | |
| 
 | |
| 	CNG_DH_CTX				*pCtx = (CNG_DH_CTX*)dh->dh;
 | |
| 	struct sshbn * bn = NULL;
 | |
| 
 | |
| 	sshbn_from(pCtx->p, pCtx->size, &bn);
 | |
| 
 | |
| 	return bn;
 | |
| }
 | |
| 
 | |
| struct sshbn *
 | |
| 	sshdh_g(struct sshdh *dh)
 | |
| {
 | |
| 	if (!cng_supported())
 | |
| 	{
 | |
| 		return bnwrap(((DH *)(dh->dh))->g);
 | |
| 	}
 | |
| 
 | |
| 	CNG_DH_CTX				*pCtx = (CNG_DH_CTX*)dh->dh;
 | |
| 	struct sshbn * bn = NULL;
 | |
| 
 | |
| 	sshbn_from(pCtx->g, pCtx->size, &bn);
 | |
| 
 | |
| 	return bn;
 | |
| }
 | |
| 
 | |
| void
 | |
| sshdh_dump(struct sshdh *dh)
 | |
| {
 | |
| 	if (!cng_supported())
 | |
| 	{
 | |
| 		DHparams_print_fp(stderr, dh->dh);
 | |
| 		fprintf(stderr, "pub= ");
 | |
| 		BN_print_fp(stderr, ((DH*)(dh->dh))->pub_key);
 | |
| 		fprintf(stderr, "\n");
 | |
| 	}
 | |
| 	return;
 | |
| }
 | |
| 
 | |
| // XXX needed?
 | |
| size_t
 | |
| sshdh_shared_key_size(struct sshdh *dh)
 | |
| {
 | |
| 	if (!cng_supported())
 | |
| 	{
 | |
| 		int sz;
 | |
| 
 | |
| 		if (dh == NULL || dh->dh == NULL || (sz = DH_size(dh->dh)) < 0)
 | |
| 			return 0;
 | |
| 		return (size_t)sz;
 | |
| 	}
 | |
| 
 | |
| 	int sz;
 | |
| 	CNG_DH_CTX				*pCtx = (CNG_DH_CTX*)dh->dh;
 | |
| 
 | |
| 	if (dh == NULL || dh->dh == NULL || (sz = pCtx->size) < 0)
 | |
| 		return 0;
 | |
| 	return (size_t)sz;
 | |
| }
 | |
| 
 | |
| // import a bignum public key 
 | |
| static BCRYPT_KEY_HANDLE
 | |
| cng_dh_set_remote_pub_key2(struct sshdh *dh, struct sshbn *b)
 | |
| {
 | |
| 
 | |
| 	BCRYPT_KEY_HANDLE hPub = NULL;
 | |
| 	CNG_DH_CTX				*pCtx = (CNG_DH_CTX*)dh->dh;
 | |
| 
 | |
| 	if (sshbn_bytes(b) > pCtx->size)
 | |
| 		return NULL;
 | |
| 
 | |
| 	DWORD cbBlob = sizeof(BCRYPT_DH_KEY_BLOB) + (pCtx->size * 3);
 | |
| 
 | |
| 	PBYTE pBlob = (PBYTE)malloc(cbBlob);
 | |
| 
 | |
| 	BCRYPT_DH_KEY_BLOB *pKeyBlob = (BCRYPT_DH_KEY_BLOB *)pBlob;
 | |
| 	pKeyBlob->cbKey = pCtx->size;
 | |
| 	pKeyBlob->dwMagic = BCRYPT_DH_PUBLIC_MAGIC;
 | |
| 	PBYTE pModulus = pBlob + sizeof(BCRYPT_DH_KEY_BLOB);
 | |
| 	PBYTE pGenerator = pModulus + pKeyBlob->cbKey;
 | |
| 	PBYTE pPublic = pGenerator + pKeyBlob->cbKey;
 | |
| 
 | |
| 	memcpy(pModulus, pCtx->p, pCtx->size);
 | |
| 	memcpy(pGenerator, pCtx->g, pCtx->size);
 | |
| 	memset(pPublic, 0, pCtx->size);
 | |
| 
 | |
| 	sshbn_to(b, pPublic + pCtx->size - (sshbn_bytes(b)));
 | |
| 
 | |
| 
 | |
| 	HRESULT Status = 0;
 | |
| 
 | |
| 	if (S_OK != (Status = BCryptImportKeyPair(pCtx->hAlg, NULL, BCRYPT_DH_PUBLIC_BLOB, &hPub, pBlob, cbBlob, 0)))
 | |
| 		goto cleanup;
 | |
| 
 | |
| cleanup:
 | |
| 	if (pBlob) free(pBlob);
 | |
| 
 | |
| 	return hPub;
 | |
| }
 | |
| 
 | |
| 
 | |
| int sshdh_compute_key(struct sshdh *dh, struct sshbn *pubkey,
 | |
| struct sshbn **shared_secretp)
 | |
| {
 | |
| 	if (!cng_supported())
 | |
| 	{
 | |
| 		u_char *sbuf;
 | |
| 		int r, slen;
 | |
| 
 | |
| 		*shared_secretp = NULL;
 | |
| 		if ((slen = DH_size(dh->dh)) <= 0)
 | |
| 			return SSH_ERR_INVALID_ARGUMENT;
 | |
| 		if ((sbuf = calloc(1, slen)) == NULL)
 | |
| 			return SSH_ERR_ALLOC_FAIL;
 | |
| 		if ((r = DH_compute_key(sbuf, pubkey->bn, dh->dh)) < 0 ||
 | |
| 			r != slen) {
 | |
| 			r = SSH_ERR_LIBCRYPTO_ERROR;
 | |
| 			goto out;
 | |
| 		}
 | |
| 		if ((r = sshbn_from(sbuf, slen, shared_secretp)) != 0)
 | |
| 			goto out;
 | |
| 		/* success */
 | |
| 		r = 0;
 | |
| 	out:
 | |
| 		explicit_bzero(sbuf, slen);
 | |
| 		free(sbuf);
 | |
| 		return r;
 | |
| 	}
 | |
| 
 | |
| 	HRESULT hr = S_OK;
 | |
| 	CNG_DH_CTX				*pCtx = (CNG_DH_CTX*)dh->dh;
 | |
| 	PBYTE pSecret = (PBYTE)malloc(pCtx->size);
 | |
| 	DWORD pSecret_len = pCtx->size;
 | |
| 	DWORD size = pCtx->size;
 | |
| 	struct sshbn * bn = NULL;
 | |
| 	BCRYPT_SECRET_HANDLE hSecret;
 | |
| 
 | |
| 	BCRYPT_KEY_HANDLE  hRemotePub = cng_dh_set_remote_pub_key2(dh, pubkey);
 | |
| 	if (hRemotePub != NULL)
 | |
| 	{
 | |
| 
 | |
| 		if (S_OK == (hr = BCryptSecretAgreement(pCtx->hPrivate, hRemotePub, &hSecret, 0)))
 | |
| 		{
 | |
| 
 | |
| 			hr = BCryptDeriveKey(hSecret, L"TRUNCATE", NULL, pSecret, pSecret_len, &size, 0);
 | |
| 			if (S_OK == hr)
 | |
| 			{
 | |
| 				cng_dh_swap_bytes(pSecret, size);
 | |
| 
 | |
| 				sshbn_from(pSecret, size, &bn);
 | |
| 				memset(pSecret, 0, size);
 | |
| 				free(pSecret);
 | |
| 				*shared_secretp = bn;
 | |
| 			}
 | |
| 			BCryptDestroySecret(hSecret);
 | |
| 		}
 | |
| 	}
 | |
| 	return S_OK == hr ? 0 : -1;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| int
 | |
| sshdh_generate(struct sshdh *dh, size_t len)
 | |
| {
 | |
| 	if (!cng_supported())
 | |
| 	{
 | |
| 		if (len > INT_MAX)
 | |
| 			return SSH_ERR_INVALID_ARGUMENT;
 | |
| 		if (len != 0)
 | |
| 			((DH*)(dh->dh))->length = (int)len;
 | |
| 		if (DH_generate_key(dh->dh) != 1)
 | |
| 			return SSH_ERR_LIBCRYPTO_ERROR;
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	HRESULT					Status;
 | |
| 	BCRYPT_DH_PARAMETER_HEADER  *DhParamHdrPointer = NULL;
 | |
| 	DWORD					DhParamBlobLength = 0;
 | |
| 	PBYTE					DhParamBlob = NULL;
 | |
| 	BCRYPT_ALG_HANDLE       hAlg = NULL;
 | |
| 	PBYTE                   pBlob = NULL;
 | |
| 	BCRYPT_KEY_HANDLE       hPriv = NULL;
 | |
| 	CNG_DH_CTX				*pCtx = (CNG_DH_CTX*)dh->dh;
 | |
| 	DWORD				    cbBlob = 0;
 | |
| 
 | |
| 
 | |
| 	DhParamBlobLength = sizeof(BCRYPT_DH_PARAMETER_HEADER) + (pCtx->size * 2);
 | |
| 
 | |
| 	if (NULL == (DhParamBlob = (PBYTE)malloc(DhParamBlobLength)))
 | |
| 		return -1;
 | |
| 
 | |
| 	DhParamHdrPointer = (BCRYPT_DH_PARAMETER_HEADER *)DhParamBlob;
 | |
| 	DhParamHdrPointer->cbLength = DhParamBlobLength;
 | |
| 	DhParamHdrPointer->cbKeyLength = pCtx->size;
 | |
| 	DhParamHdrPointer->dwMagic = BCRYPT_DH_PARAMETERS_MAGIC;
 | |
| 
 | |
| 	memcpy(DhParamBlob + sizeof(BCRYPT_DH_PARAMETER_HEADER), pCtx->p, pCtx->size);
 | |
| 	memcpy(DhParamBlob + sizeof(BCRYPT_DH_PARAMETER_HEADER) + pCtx->size, pCtx->g, pCtx->size);
 | |
| 
 | |
| 	if (S_OK != (Status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_DH_ALGORITHM, NULL, 0)))
 | |
| 		goto error;
 | |
| 
 | |
| 
 | |
| 	if (S_OK != (Status = BCryptGenerateKeyPair(hAlg, &hPriv, pCtx->size * 8, 0)))
 | |
| 		goto error;
 | |
| 
 | |
| 
 | |
| 	if (S_OK != (Status = BCryptSetProperty(hPriv, BCRYPT_DH_PARAMETERS, DhParamBlob, DhParamBlobLength, 0)))
 | |
| 		goto error;
 | |
| 
 | |
| 	if (S_OK != (Status = BCryptFinalizeKeyPair(hPriv, 0)))
 | |
| 		goto error;
 | |
| 
 | |
| 	if (S_OK != (Status = BCryptExportKey(hPriv, NULL, BCRYPT_DH_PUBLIC_BLOB, NULL, 0, &cbBlob, 0)))
 | |
| 		goto error;
 | |
| 
 | |
| 	if (NULL == (pBlob = (PBYTE)malloc(cbBlob)))
 | |
| 	{
 | |
| 		Status = STATUS_NO_MEMORY;
 | |
| 		goto error;
 | |
| 	}
 | |
| 
 | |
| 	if (S_OK != (Status = BCryptExportKey(hPriv, NULL, BCRYPT_DH_PUBLIC_BLOB, pBlob, cbBlob, &cbBlob, 0)))
 | |
| 		goto error;
 | |
| 
 | |
| 
 | |
| 
 | |
| 	BCRYPT_DH_KEY_BLOB *pKeyBlob = (BCRYPT_DH_KEY_BLOB *)pBlob;
 | |
| 	PBYTE pModulus = pBlob + sizeof(BCRYPT_DH_KEY_BLOB);
 | |
| 	PBYTE pGenerator = pModulus + pKeyBlob->cbKey;
 | |
| 	PBYTE pPublic = pGenerator + pKeyBlob->cbKey;
 | |
| 
 | |
| 	pCtx->hAlg = hAlg;
 | |
| 	pCtx->hPrivate = hPriv;
 | |
| 	memcpy(pCtx->pub, pPublic, pCtx->size);
 | |
| 
 | |
| 	return 0;
 | |
| error:
 | |
| 
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| int
 | |
| sshdh_new_group_hex(const char *gen, const char *modulus, struct sshdh **dhp)
 | |
| {
 | |
| 	if (!cng_supported())
 | |
| 	{
 | |
| 		struct sshdh *ret;
 | |
| 
 | |
| 		*dhp = NULL;
 | |
| 		if ((ret = sshdh_new()) == NULL)
 | |
| 			return SSH_ERR_ALLOC_FAIL;
 | |
| 		if (BN_hex2bn(&(((DH*)(ret->dh))->p), modulus) == 0 ||
 | |
| 			BN_hex2bn(&(((DH*)(ret->dh))->g), gen) == 0) {
 | |
| 			sshdh_free(ret);
 | |
| 			return SSH_ERR_LIBCRYPTO_ERROR;
 | |
| 		}
 | |
| 		*dhp = ret;
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	struct sshdh *ret;
 | |
| 	struct sshbn * g = NULL;
 | |
| 	struct sshbn * p = NULL;
 | |
| 
 | |
| 	sshbn_from_hex(gen, &g);
 | |
| 	sshbn_from_hex(modulus, &p);
 | |
| 	*dhp = NULL;
 | |
| 
 | |
| 	ret = sshdh_new_group(g, p);
 | |
| 
 | |
| 	if (g != NULL)
 | |
| 		sshbn_free(g);
 | |
| 	if (p != NULL)
 | |
| 		sshbn_free(p);
 | |
| 
 | |
| 	*dhp = ret;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /* XXX transfers ownership of gen, modulus */
 | |
| struct sshdh *
 | |
| 	sshdh_new_group(struct sshbn *gen, struct sshbn *modulus)
 | |
| {
 | |
| 	if (!cng_supported())
 | |
| 	{
 | |
| 		struct sshdh *dh;
 | |
| 
 | |
| 		if ((dh = sshdh_new()) == NULL)
 | |
| 			return NULL;
 | |
| 		((DH*)(dh->dh))->p = modulus->bn;
 | |
| 		((DH*)(dh->dh))->g = gen->bn;
 | |
| 		modulus->bn = gen->bn = NULL;
 | |
| 		sshbn_free(gen);
 | |
| 		sshbn_free(modulus);
 | |
| 		return (dh);
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	struct sshdh *dh;
 | |
| 
 | |
| 	PBYTE pBlob = NULL;
 | |
| 	DWORD keysize = sshbn_bytes(modulus);
 | |
| 	DWORD cbBlob = 0;
 | |
| 
 | |
| 	dh = sshdh_new();
 | |
| 
 | |
| 	pBlob = (PBYTE)malloc(sizeof(CNG_DH_CTX) + (3 * keysize));
 | |
| 	memset(pBlob, 0, sizeof(CNG_DH_CTX) + (3 * keysize));
 | |
| 
 | |
| 	CNG_DH_CTX * pCtx = (CNG_DH_CTX *)pBlob;
 | |
| 
 | |
| 	pCtx->size = keysize;
 | |
| 	pCtx->p = pBlob + sizeof(CNG_DH_CTX);
 | |
| 	pCtx->g = pCtx->p + keysize;
 | |
| 	pCtx->pub = pCtx->g + keysize;
 | |
| 
 | |
| 	sshbn_to(gen, pCtx->g + keysize - sshbn_bytes(gen));
 | |
| 	sshbn_to(modulus, pCtx->p + keysize - sshbn_bytes(modulus));
 | |
| 
 | |
| 	dh->dh = (void *)pCtx;
 | |
| 	return dh;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 |