333 lines
7.1 KiB
C
333 lines
7.1 KiB
C
/*
|
|
* Author: Microsoft Corp.
|
|
*
|
|
* Copyright (c) 2015 Microsoft Corp.
|
|
* All rights reserved
|
|
*
|
|
* Microsoft openssh win32 port
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
/* cng_cipher.c
|
|
*
|
|
* Openssh ciphers implemented using Microsoft Crypto Next Generation (CNG).
|
|
*
|
|
*/
|
|
|
|
#include <Windows.h>
|
|
#include <bcrypt.h>
|
|
|
|
#include "cng_cipher.h"
|
|
|
|
#ifdef USE_MSCNG
|
|
|
|
|
|
#define AES_BLOCK_SIZE 16
|
|
|
|
|
|
/*
|
|
* increment the aes counter (iv)
|
|
*/
|
|
static void aesctr_inc(unsigned char *ctr, unsigned int len)
|
|
{
|
|
size_t i;
|
|
|
|
#ifndef CONSTANT_TIME_INCREMENT
|
|
for (i = len - 1; i >= 0; i--)
|
|
if (++ctr[i]) /* continue on overflow */
|
|
return;
|
|
#else
|
|
u8 x, add = 1;
|
|
|
|
for (i = len - 1; i >= 0; i--) {
|
|
ctr[i] += add;
|
|
/* constant time for: x = ctr[i] ? 1 : 0 */
|
|
x = ctr[i];
|
|
x = (x | (x >> 4)) & 0xf;
|
|
x = (x | (x >> 2)) & 0x3;
|
|
x = (x | (x >> 1)) & 0x1;
|
|
add *= (x ^ 1);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
* Routine to encrypt a counter for ctr encryption. This requries
|
|
* us to use an IV that is reset for each call to avoid cng attempting
|
|
* to chain encryptions.
|
|
*/
|
|
DWORD cng_counter_encrypt(const unsigned char *in, unsigned char *out, BCRYPT_KEY_HANDLE key, unsigned int blocklen)
|
|
{
|
|
HRESULT status = S_OK;
|
|
DWORD cbResult = 0;
|
|
|
|
unsigned char iv[AES_BLOCK_SIZE] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
|
|
status = BCryptEncrypt(
|
|
key,
|
|
(PUCHAR)in,
|
|
blocklen,
|
|
NULL,
|
|
iv,
|
|
blocklen,
|
|
out,
|
|
blocklen,
|
|
&cbResult,
|
|
0);
|
|
|
|
return cbResult;
|
|
}
|
|
|
|
/*
|
|
* Encrypt/Decrypt data using a CTR mode.
|
|
* In this mode, we can't call CNG encryption/decription directly. The mode requires
|
|
* the use of the iv as a counter that is incremented and encrypted. The
|
|
* encrypted counter is then XORd with the data to produce the cipher text.
|
|
*/
|
|
int cng_aesctr_encrypt_bytes(PSSH_CNG_CIPHER_CTX x, const unsigned char *m, unsigned char *c, unsigned int bytes)
|
|
{
|
|
int ret = 0;
|
|
unsigned int n = 0;
|
|
unsigned char buf[AES_BLOCK_SIZE];
|
|
|
|
while ((bytes--) > 0) {
|
|
if (n == 0) {
|
|
if (!cng_counter_encrypt(x->pbIV, buf, x->hKey, AES_BLOCK_SIZE))
|
|
{
|
|
ret = -1;
|
|
break;
|
|
}
|
|
aesctr_inc(x->pbIV, AES_BLOCK_SIZE);
|
|
}
|
|
*(c++) = *(m++) ^ buf[n];
|
|
n = (n + 1) % AES_BLOCK_SIZE;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Encrypt data using a provided cipher context
|
|
*/
|
|
unsigned int cng_cipher_encrypt(PSSH_CNG_CIPHER_CTX x, unsigned char *dest, unsigned int dest_len, const unsigned char *src, unsigned int len)
|
|
{
|
|
DWORD cbResult = 0;
|
|
HRESULT status = S_OK;
|
|
|
|
if (x->flags & _CNG_MODE_CTR)
|
|
{
|
|
if (-1 == cng_aesctr_encrypt_bytes(x, src, dest, len))
|
|
{
|
|
status = GetLastError();
|
|
}
|
|
cbResult = len;
|
|
}
|
|
else
|
|
{
|
|
|
|
status = BCryptEncrypt(
|
|
x->hKey,
|
|
(PUCHAR)src,
|
|
len,
|
|
NULL,
|
|
x->pbIV,
|
|
x->cbBlockSize,
|
|
dest,
|
|
dest_len,
|
|
&cbResult,
|
|
0);
|
|
if (S_OK != status)
|
|
{
|
|
cbResult = 0;
|
|
SetLastError(status);
|
|
}
|
|
}
|
|
return cbResult;
|
|
}
|
|
|
|
/*
|
|
* Decrypt encrypted data using a provided cipher context
|
|
*/
|
|
unsigned int cng_cipher_decrypt(PSSH_CNG_CIPHER_CTX x, unsigned char *dest, unsigned int dest_len, const unsigned char *src, unsigned int len)
|
|
{
|
|
DWORD cbResult = 0;
|
|
HRESULT status = S_OK;
|
|
|
|
if (x->flags & _CNG_MODE_CTR)
|
|
{
|
|
// ctr mode is just an XOR so encrypt=decrypt
|
|
if (-1 == cng_aesctr_encrypt_bytes(x, src, dest, len))
|
|
{
|
|
status = GetLastError();
|
|
}
|
|
cbResult = len;
|
|
}
|
|
else
|
|
{
|
|
|
|
status = BCryptDecrypt(
|
|
x->hKey,
|
|
(PUCHAR)src,
|
|
len,
|
|
NULL,
|
|
x->pbIV,
|
|
x->cbBlockSize,
|
|
dest,
|
|
dest_len,
|
|
&cbResult,
|
|
0);
|
|
if (S_OK != status)
|
|
{
|
|
cbResult = 0;
|
|
SetLastError(status);
|
|
}
|
|
}
|
|
return cbResult;
|
|
}
|
|
|
|
|
|
/*
|
|
* Initialize cipher context
|
|
*/
|
|
unsigned int cng_cipher_init(PSSH_CNG_CIPHER_CTX x, const unsigned char *key, unsigned int keylen, const unsigned char *iv, size_t ivlen, unsigned int flags)
|
|
{
|
|
HRESULT status = S_OK;
|
|
BCRYPT_ALG_HANDLE hAlg = NULL;
|
|
DWORD cbData = 0;
|
|
LPCWSTR pAlg = NULL;
|
|
DWORD cbBlockLen = 0;
|
|
DWORD cbKeyObject = 0;
|
|
|
|
if ((0 == (flags & _CNG_CIPHER_AES)) || (0 == (flags & (_CNG_MODE_CBC | _CNG_MODE_CTR))))
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
|
|
|
|
// wipe out old context
|
|
memset(x, 0, sizeof(SSH_CNG_CIPHER_CTX));
|
|
|
|
|
|
// initialize simple context fields
|
|
x->flags = flags;
|
|
|
|
// only one cipher supported right now
|
|
if (flags & _CNG_CIPHER_AES)
|
|
pAlg = BCRYPT_AES_ALGORITHM;
|
|
|
|
|
|
// Generate BCrypt Key and set mode if applicable
|
|
if (NT_SUCCESS(status = BCryptOpenAlgorithmProvider(
|
|
&hAlg,
|
|
pAlg,
|
|
NULL,
|
|
0)))
|
|
{
|
|
|
|
if (NT_SUCCESS(status = BCryptGetProperty(
|
|
hAlg,
|
|
BCRYPT_BLOCK_LENGTH,
|
|
(PBYTE)&cbBlockLen,
|
|
sizeof(DWORD),
|
|
&cbData,
|
|
0)))
|
|
{
|
|
x->cbBlockSize = cbBlockLen;
|
|
if (cbBlockLen != ivlen)
|
|
{
|
|
status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
else
|
|
{
|
|
x->pbIV = (PBYTE)HeapAlloc(GetProcessHeap(), 0, ivlen);
|
|
if (NULL == x->pbIV)
|
|
{
|
|
status = GetLastError();
|
|
}
|
|
else
|
|
{
|
|
memcpy(x->pbIV, iv, ivlen);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (status == S_OK && flags & _CNG_MODE_CBC)
|
|
{
|
|
status = BCryptSetProperty(
|
|
hAlg,
|
|
BCRYPT_CHAINING_MODE,
|
|
(PBYTE)BCRYPT_CHAIN_MODE_CBC,
|
|
sizeof(BCRYPT_CHAIN_MODE_CBC),
|
|
0);
|
|
}
|
|
|
|
if (status == S_OK)
|
|
{
|
|
status = BCryptGetProperty(
|
|
hAlg,
|
|
BCRYPT_OBJECT_LENGTH,
|
|
(PBYTE)&cbKeyObject,
|
|
sizeof(DWORD),
|
|
&cbData,
|
|
0);
|
|
}
|
|
|
|
if ((status == S_OK) && (x->pKeyObject = (PBYTE)HeapAlloc(GetProcessHeap(),0,cbKeyObject)))
|
|
{
|
|
status = BCryptGenerateSymmetricKey(
|
|
hAlg,
|
|
&(x->hKey),
|
|
x->pKeyObject,
|
|
cbKeyObject,
|
|
(PBYTE)key,
|
|
keylen,
|
|
0);
|
|
}
|
|
BCryptCloseAlgorithmProvider(hAlg, 0);
|
|
|
|
// if we got an error along the way, free up the iv and key object
|
|
if (status != S_OK && x->pbIV)
|
|
{
|
|
HeapFree(GetProcessHeap(), 0, x->pbIV);
|
|
}
|
|
if (status != S_OK && x->pKeyObject)
|
|
{
|
|
HeapFree(GetProcessHeap(), 0, x->pKeyObject);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
/*
|
|
* Cleanup cipher context fields
|
|
*/
|
|
void cng_cipher_cleanup(PSSH_CNG_CIPHER_CTX x)
|
|
{
|
|
if (x->pbIV)
|
|
HeapFree(GetProcessHeap(), 0, x->pbIV);
|
|
if (x->hKey)
|
|
BCryptDestroyKey(x->hKey);
|
|
if (x->pKeyObject)
|
|
HeapFree(GetProcessHeap(), 0, x->pKeyObject);
|
|
}
|
|
|
|
#endif |