xsec/enc/WinCAPI/WinCAPICryptoSymmetricKey.cpp (441 lines of code) (raw):

/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* * XSEC * * XSECCryptoSymmetricKey := Bulk encryption algorithms should all be * implemented via this interface * * $Id$ * */ #include <xsec/enc/XSECCryptoException.hpp> #include <xsec/enc/WinCAPI/WinCAPICryptoProvider.hpp> #include <xsec/enc/WinCAPI/WinCAPICryptoSymmetricKey.hpp> #include <xsec/framework/XSECDefs.hpp> #include <xsec/framework/XSECError.hpp> #include <xsec/utils/XSECPlatformUtils.hpp> XERCES_CPP_NAMESPACE_USE #if defined (XSEC_HAVE_WINCAPI) #include "../../utils/XSECDOMUtils.hpp" #include <xercesc/util/Janitor.hpp> // -------------------------------------------------------------------------------- // Constructors and Destructors // -------------------------------------------------------------------------------- WinCAPICryptoSymmetricKey::WinCAPICryptoSymmetricKey( HCRYPTPROV prov, XSECCryptoSymmetricKey::SymmetricKeyType type) : m_keyType(type), m_keyMode(MODE_NONE), m_initialised(false), m_doPad(true), m_p(prov), m_k(0) { } WinCAPICryptoSymmetricKey::~WinCAPICryptoSymmetricKey() { if (m_k != 0) CryptDestroyKey(m_k); } // -------------------------------------------------------------------------------- // Basic Key interface methods // -------------------------------------------------------------------------------- XSECCryptoKey * WinCAPICryptoSymmetricKey::clone() const { WinCAPICryptoSymmetricKey * ret; XSECnew(ret, WinCAPICryptoSymmetricKey(m_p, m_keyType)); ret->m_keyLen = m_keyLen; ret->m_keyBuf = m_keyBuf; if (m_k != 0) { #if (_WIN32_WINNT > 0x0400) // Only supported in Win2K and above if (CryptDuplicateKey(m_k, 0, 0, &(ret->m_k)) == 0 ) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey Error attempting to clone key parameters"); } #else ret->setKey(m_keyBuf.rawBuffer(), m_keyLen); #endif } else ret->m_k = 0; return ret; } // -------------------------------------------------------------------------------- // Store the key value // -------------------------------------------------------------------------------- void WinCAPICryptoSymmetricKey::setKey(const unsigned char * key, unsigned int keyLen) { m_keyBuf.sbMemcpyIn(key, keyLen); m_keyLen = keyLen; if (m_k != 0) CryptDestroyKey(m_k); m_p = 0; m_k = createWindowsKey(key, keyLen, m_keyType, &m_p); } // -------------------------------------------------------------------------------- // Decrypt // -------------------------------------------------------------------------------- int WinCAPICryptoSymmetricKey::decryptCtxInit(const unsigned char * iv) { // Returns amount of IV data used (in bytes) // Sets m_initialised iff the key is OK and the IV is OK. if (m_initialised) return 0; if (m_k == 0) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Cannot initialise without key"); } else if (m_keyMode == MODE_NONE) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Cannot initialise without mode"); } DWORD cryptMode; if (m_keyMode == MODE_CBC) { if (iv == NULL) { return 0; // Cannot initialise without an IV } cryptMode = CRYPT_MODE_CBC; if (!CryptSetKeyParam(m_k, KP_MODE, (BYTE *) (&cryptMode), 0)) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Error setting cipher mode"); } if (!CryptSetKeyParam(m_k, KP_IV, (unsigned char *) iv, 0)) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Error setting IV"); } } else if (m_keyMode == MODE_ECB) { cryptMode = CRYPT_MODE_ECB; if (!CryptSetKeyParam(m_k, KP_MODE, (BYTE *) (&cryptMode), 0)) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Error setting cipher mode"); } } else { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Unknown cipher mode"); } // Set up the context according to the required cipher type switch (m_keyType) { case (XSECCryptoSymmetricKey::KEY_3DES_192) : // A 3DES CBC key m_blockSize = 8; m_bytesInLastBlock = 0; m_initialised = true; break; case (XSECCryptoSymmetricKey::KEY_AES_128) : case (XSECCryptoSymmetricKey::KEY_AES_192) : case (XSECCryptoSymmetricKey::KEY_AES_256) : // An AES key m_blockSize = 16; m_bytesInLastBlock = 0; m_initialised = true; break; default : // Cannot do this without an IV throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Unknown key type"); } // Setup ivSize switch (m_keyMode) { case MODE_CBC: m_ivSize = m_blockSize; break; case MODE_GCM: m_ivSize = 12; break; default: m_ivSize = 0; } return m_ivSize; } bool WinCAPICryptoSymmetricKey::decryptInit(bool doPad, SymmetricKeyMode mode, const unsigned char* iv, const unsigned char* tag, unsigned int taglen) { m_initialised = false; m_doPad = doPad; m_keyMode = mode; decryptCtxInit(iv); return true; } unsigned int WinCAPICryptoSymmetricKey::decrypt(const unsigned char * inBuf, unsigned char * plainBuf, unsigned int inLength, unsigned int maxOutLength) { // NOTE: This won't actually stop WinCAPI blowing the buffer, so the onus is // on the caller. unsigned int offset = 0; if (!m_initialised) { offset = decryptCtxInit(inBuf); if (offset > inLength) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Not enough data passed in to get IV"); } } /* As per bug #38365 - we need to ensure there are enough characters to decrypt. Otherwise we get some nasty errors due to rounding calculations when inputs are too small */ else if (m_bytesInLastBlock + inLength < m_blockSize) { // Not enough input data memcpy(&m_lastBlock[m_bytesInLastBlock], inBuf, inLength); m_bytesInLastBlock += inLength; return 0; } DWORD outl = inLength - offset; // Copy in last block if (m_bytesInLastBlock > 0) memcpy(plainBuf, m_lastBlock, m_bytesInLastBlock); // Copy out the tail, as we _MUST_ know when we come to the end for decryptFinal unsigned int rounding = (outl % m_blockSize) + m_blockSize; memcpy(&plainBuf[m_bytesInLastBlock], &inBuf[offset], outl - rounding); // Copy the tail to m_lastBlock memcpy(m_lastBlock, &inBuf[offset + outl - rounding], rounding); outl = outl - rounding + m_bytesInLastBlock; m_bytesInLastBlock = rounding; if (!CryptDecrypt(m_k, 0, FALSE, 0, plainBuf, &outl)) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Error during WinCAPI decrypt"); } return outl; } unsigned int WinCAPICryptoSymmetricKey::decryptFinish(unsigned char * plainBuf, unsigned int maxOutLength) { DWORD outl = m_bytesInLastBlock; memcpy(plainBuf, m_lastBlock, outl); if (!CryptDecrypt(m_k, 0, FALSE, 0, plainBuf, &outl)) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Error during WinCAPI decrypt finalisation"); } if (m_doPad) { // Need to do this ourselves, as WinCAPI appears broken if (plainBuf[outl - 1] > m_blockSize) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Bad padding"); } outl -= plainBuf[outl - 1]; } // This is just to reset the key, and does nothing else useful. DWORD tmpout = maxOutLength - outl; CryptDecrypt(m_k, 0, TRUE, 0, &plainBuf[outl], &tmpout); return outl; } // -------------------------------------------------------------------------------- // Encrypt // -------------------------------------------------------------------------------- void WinCAPICryptoSymmetricKey::encryptCtxInit(const unsigned char * iv) { if (m_initialised == true) return; if (m_keyLen == 0) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Cannot initialise without key"); } else if (m_keyMode == MODE_NONE) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Cannot initialise without mode"); } m_initialised = true; // Set up the context according to the required cipher type const unsigned char * usedIV = NULL; unsigned char genIV[256]; DWORD cryptMode; // Tell the library that the IV still has to be sent if (m_keyMode == MODE_CBC) { if (iv == NULL) { BOOL res = CryptGenRandom(m_p, 256, genIV); if (res == FALSE) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Error generating random IV"); } usedIV = genIV; //return 0; // Cannot initialise without an IV } else usedIV = iv; cryptMode = CRYPT_MODE_CBC; if (!CryptSetKeyParam(m_k, KP_MODE, (BYTE *) (&cryptMode), 0)) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Error setting cipher mode"); } // Set the IV parameter if (!CryptSetKeyParam(m_k, KP_IV, (unsigned char *) usedIV, 0)) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Error setting IV"); } } else if (m_keyMode == MODE_ECB) { cryptMode = CRYPT_MODE_ECB; if (!CryptSetKeyParam(m_k, KP_MODE, (BYTE *) (&cryptMode), 0)) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Error setting cipher mode"); } } else { throw XSECCryptoException(XSECCryptoException::SymmetricError, "OpenSSL:SymmetricKey - Unsupported cipher mode"); } switch (m_keyType) { case (XSECCryptoSymmetricKey::KEY_3DES_192) : // A 3DES key m_blockSize = 8; if (m_keyMode == MODE_CBC) m_ivSize = 8; else m_ivSize = 0; m_bytesInLastBlock = 0; break; case (XSECCryptoSymmetricKey::KEY_AES_128) : case (XSECCryptoSymmetricKey::KEY_AES_192) : case (XSECCryptoSymmetricKey::KEY_AES_256) : // An AES key m_blockSize = 16; if (m_keyMode == MODE_CBC) m_ivSize = 16; else if (m_keyMode == MODE_GCM) m_ivSize = 12; else m_ivSize = 0; m_bytesInLastBlock = 0; break; default : // Cannot do this without an IV throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Unknown key type"); } if (usedIV != NULL && m_ivSize > 0) memcpy(m_lastBlock, usedIV, m_ivSize); } bool WinCAPICryptoSymmetricKey::encryptInit(bool doPad, SymmetricKeyMode mode, const unsigned char * iv) { m_doPad = doPad; m_keyMode = mode; m_initialised = false; encryptCtxInit(iv); return true; } unsigned int WinCAPICryptoSymmetricKey::encrypt(const unsigned char * inBuf, unsigned char * cipherBuf, unsigned int inLength, unsigned int maxOutLength) { if (m_initialised == false) { encryptInit(); } // NOTE: This won't actually stop WinCAPI blowing the buffer, so the onus is // on the caller. unsigned int offset = 0; unsigned char * bufPtr; unsigned char * encPtr; // Ptr to start of block to encrypt DWORD outl = 0; if (m_ivSize > 0) { DWORD len = m_ivSize; if (!CryptGetKeyParam(m_k, KP_IV, (unsigned char *) m_lastBlock, &len, 0)) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Error setting IV"); } memcpy(cipherBuf, m_lastBlock, m_ivSize); offset = m_ivSize; outl += m_ivSize; m_ivSize = 0; } /* As per bug #38365 - we need to ensure there are enough characters to encrypt. Otherwise we get some nasty errors due to rounding calculations when inputs are too small */ if (inLength < m_blockSize) { // Not enough input data memcpy(&m_lastBlock[m_bytesInLastBlock], inBuf, inLength); m_bytesInLastBlock += inLength; // Just in case we have returned an IV return outl; } bufPtr = &cipherBuf[offset]; encPtr = bufPtr; if (m_bytesInLastBlock > 0) { memcpy(bufPtr, m_lastBlock, m_bytesInLastBlock); bufPtr = &bufPtr[m_bytesInLastBlock]; outl += m_bytesInLastBlock; } unsigned int rounding = (m_bytesInLastBlock + inLength) % m_blockSize; rounding += m_blockSize; outl += inLength - rounding; if (outl > maxOutLength) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Not enough space in output buffer for encrypt"); } outl -= offset; // Now copy back one block + rounding memcpy(m_lastBlock, &inBuf[inLength - rounding], rounding); m_bytesInLastBlock = rounding; // Finally, copy in last of buffer to encrypt memcpy(bufPtr, inBuf, inLength - rounding); // Do the enrypt if (!CryptEncrypt(m_k, 0, FALSE, 0, encPtr, &outl, maxOutLength)) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Error during WinCAPI encrypt"); } return outl + offset; } unsigned int WinCAPICryptoSymmetricKey::encryptFinish(unsigned char * cipherBuf, unsigned int maxOutLength, unsigned int taglen) { DWORD outl = m_bytesInLastBlock + m_blockSize; if (outl > maxOutLength) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Not enough space in output buffer for encrypt - NOTE WinCAPI requires an extra block to complete encryption"); } outl = 0; if (m_bytesInLastBlock != 0) { memcpy(cipherBuf, m_lastBlock, m_bytesInLastBlock); outl = m_bytesInLastBlock; } if (!CryptEncrypt(m_k, 0, TRUE, 0, cipherBuf, &outl, maxOutLength)) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Error during WinCAPI encrypt"); } if (!m_doPad) { // It is the responsibility of the caller to ensure they have // passed in block size num bytes if (outl >= m_blockSize) outl -= m_blockSize; } return outl; } // -------------------------------------------------------------------------------- // Create a windows key // -------------------------------------------------------------------------------- HCRYPTKEY WinCAPICryptoSymmetricKey::createWindowsKey( const unsigned char * key, unsigned int keyLen, XSECCryptoSymmetricKey::SymmetricKeyType type, HCRYPTPROV * prov) { // First get the correct Provider handle to load the key into HCRYPTPROV p; if (prov == NULL || *prov == 0) { if (!strEquals(XSECPlatformUtils::g_cryptoProvider->getProviderName(), DSIGConstants::s_unicodeStrPROVWinCAPI)) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Main provider is not Windows provider"); } WinCAPICryptoProvider * cp = (WinCAPICryptoProvider*) XSECPlatformUtils::g_cryptoProvider; p = cp->getApacheKeyStore(); if (p == 0) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Unable to retrieve internal key store"); } if (prov != NULL) *prov = p; } else if (prov != NULL) p = *prov; // Get the key wrapping key HCRYPTKEY k; if (!CryptGetUserKey(p, AT_KEYEXCHANGE, &k)) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Unable to retrieve internal key pair"); } // Find out how long the output will be DWORD outl = 0; if (!CryptEncrypt(k, 0, TRUE, 0, 0, &outl, keyLen)) { DWORD error = GetLastError(); if (error == NTE_BAD_KEY) { // We throw either way, but this is *likely* to be an unsupported OS issue throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey::createWindowsKey - Error encrypting a key - is this >= Windows 2000?"); } throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Unable to determine space required to encrypt key"); } // Create the necessary buffer unsigned char * encryptBuf; unsigned int encryptBufSize = outl; XSECnew(encryptBuf, unsigned char[outl]); ArrayJanitor<unsigned char> j_encryptBuf(encryptBuf); memcpy(encryptBuf, key, keyLen); outl = keyLen; // Do the encrypt if (!CryptEncrypt(k, 0, TRUE, 0, encryptBuf, &outl, encryptBufSize)) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Unable to encrypt key"); } // Now we have the encrypted buffer, create a SIMPLEBLOB structure unsigned char * simpleBlob; XSECnew(simpleBlob, unsigned char [sizeof (BLOBHEADER) + sizeof (DWORD) + outl]); ArrayJanitor<unsigned char> j_simpleBlob(simpleBlob); BLOBHEADER * blobHeader = (BLOBHEADER *) simpleBlob; blobHeader->bType = SIMPLEBLOB; blobHeader->bVersion = CUR_BLOB_VERSION; blobHeader->reserved = 0; unsigned int expectedLength; switch (type) { case (XSECCryptoSymmetricKey::KEY_3DES_192) : blobHeader->aiKeyAlg = CALG_3DES; expectedLength = 24; break; case (XSECCryptoSymmetricKey::KEY_AES_128) : blobHeader->aiKeyAlg = CALG_AES_128; expectedLength = 16; break; case (XSECCryptoSymmetricKey::KEY_AES_192) : blobHeader->aiKeyAlg = CALG_AES_192; expectedLength = 24; break; case (XSECCryptoSymmetricKey::KEY_AES_256) : blobHeader->aiKeyAlg = CALG_AES_256; expectedLength = 32; break; default : throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey::createWindowsKey - Unknown Symmetric key type"); } // Check key length - otherwise the user could get some very cryptic error messages if (keyLen != expectedLength) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey::createWindowsKey - Key length incorrect for algorithm"); } DWORD * algId = (DWORD *) (simpleBlob + sizeof(BLOBHEADER)); *algId = CALG_RSA_KEYX; // Copy in the encrypted data memcpy(&simpleBlob[sizeof(BLOBHEADER) + sizeof(DWORD)], encryptBuf, outl); // Now do the import HCRYPTKEY k2; if (!CryptImportKey(p, simpleBlob, sizeof(BLOBHEADER) + sizeof(DWORD) + outl, k, CRYPT_EXPORTABLE, &k2)) { throw XSECCryptoException(XSECCryptoException::SymmetricError, "WinCAPI:SymmetricKey - Unable to import key"); } return k2; } #endif /* XSEC_HAVE_WINCAPI */