Win32-OpenSSH/contrib/win32/win32compat/cng_cipher.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