/
githubmirror
/
xmlsec
Обзор
Документация
Войти
/
githubmirror
/
xmlsec
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/mscng/certkeys_dh.c
759 строк
25 KB
Javid Khan
fix double-free in mscng dh key adoption on SetDhQ failure (#1183)
28 май 2026, 16:28
Не верифицирован
28 май 2026, 16:28
639b1eb
Код
Авторство
О чём код?
/** * XML Security Library (http://www.aleksey.com/xmlsec). * * This is free software; see the Copyright file in the source distribution for precise wording. * * Copyright (C) 2018-2026 Aleksey Sanin <aleksey@aleksey.com>. All Rights Reserved. * Copyright (C) 2018 Miklos Vajna. All Rights Reserved. */ /** * @addtogroup xmlsec_mscng_certkeys * @brief DH key support functions for Microsoft Cryptography API: Next Generation (CNG). */ #include "globals.h" #include <string.h> #include <xmlsec/xmlsec.h> #include <xmlsec/errors.h> #include <xmlsec/keys.h> #include <xmlsec/private.h> #include <xmlsec/buffer.h> #include <xmlsec/mscng/certkeys.h> #include <xmlsec/mscng/crypto.h> #include "../cast_helpers.h" #include "../keysdata_helpers.h" #include "private.h" #ifndef XMLSEC_NO_DH int xmlSecMSCngKeyDataDuplicateBCryptDhPrivKey(BCRYPT_KEY_HANDLE src, BCRYPT_KEY_HANDLE* dst) { BCRYPT_ALG_HANDLE hDhAlg = NULL; DWORD cbPrivBlob = 0; PUCHAR pbPrivBlob; BCRYPT_KEY_HANDLE hDhAlgKey = NULL; NTSTATUS status; xmlSecAssert2(src != NULL, -1); xmlSecAssert2(dst != NULL, -1); /* export DH private key blob */ status = BCryptExportKey(src, NULL, BCRYPT_DH_PRIVATE_BLOB, NULL, 0, &cbPrivBlob, 0); if(status != STATUS_SUCCESS) { xmlSecMSCngNtError("BCryptExportKey(DH priv)", NULL, status); return(-1); } pbPrivBlob = (PUCHAR)xmlMalloc(cbPrivBlob); if(pbPrivBlob == NULL) { xmlSecMallocError(cbPrivBlob, NULL); return(-1); } status = BCryptExportKey(src, NULL, BCRYPT_DH_PRIVATE_BLOB, pbPrivBlob, cbPrivBlob, &cbPrivBlob, 0); if(status != STATUS_SUCCESS) { xmlSecMSCngNtError("BCryptExportKey2(DH priv)", NULL, status); xmlFree(pbPrivBlob); return(-1); } status = BCryptOpenAlgorithmProvider(&hDhAlg, BCRYPT_DH_ALGORITHM, NULL, 0); if(status != STATUS_SUCCESS) { xmlSecMSCngNtError("BCryptOpenAlgorithmProvider(DH priv dup)", NULL, status); xmlFree(pbPrivBlob); return(-1); } status = BCryptImportKeyPair(hDhAlg, NULL, BCRYPT_DH_PRIVATE_BLOB, &hDhAlgKey, pbPrivBlob, cbPrivBlob, 0); BCryptCloseAlgorithmProvider(hDhAlg, 0); xmlFree(pbPrivBlob); if(status != STATUS_SUCCESS) { xmlSecMSCngNtError("BCryptImportKeyPair(DH priv dup)", NULL, status); return(-1); } *dst = hDhAlgKey; return(0); } /* DER parsing helpers */ /* Read a DER TLV: returns pointer to value bytes and sets *pLen. Tag must match expectedTag. Returns NULL on error. */ static const xmlSecByte* xmlSecMSCngDerReadTlv(const xmlSecByte* p, const xmlSecByte* end, BYTE expectedTag, DWORD* pLen) { DWORD len; if(p >= end || *p != expectedTag) { return(NULL); } p++; if(p >= end) { return(NULL); } if(*p & 0x80) { BYTE nBytes = (*p) & 0x7F; p++; if(nBytes == 0 || nBytes > 4 || p + nBytes > end) { return(NULL); } len = 0; while(nBytes-- > 0) { len = (len << 8) | (*p++); } } else { len = *p++; } if(p + len > end) { return(NULL); } *pLen = len; return(p); } /* Parse DER INTEGER → strip leading sign byte if present. * Returns pointer to big-endian integer bytes (inside src buffer), sets *pLen. */ const xmlSecByte* xmlSecMSCngDerDecodeInteger(const xmlSecByte* p, const xmlSecByte* end, DWORD* pLen) { const xmlSecByte* val; DWORD len; val = xmlSecMSCngDerReadTlv(p, end, 0x02 /* INTEGER */, &len); if(val == NULL || len == 0) { return(NULL); } /* skip optional leading zero (sign byte) */ if(len > 1 && *val == 0x00) { val++; len--; } *pLen = len; return(val); } /* Parse DH AlgorithmIdentifier parameters: SEQUENCE { INTEGER p, INTEGER g [, INTEGER q] } * On success sets output pointers and lengths for p and g (big-endian, no sign byte). */ int xmlSecMSCngDhParseDhParameters(const xmlSecByte* params, DWORD paramsLen, const xmlSecByte** ppP, DWORD* pPLen, const xmlSecByte** ppG, DWORD* pGLen, const xmlSecByte** ppQ, DWORD* pQLen) { const xmlSecByte* end = params + paramsLen; const xmlSecByte* seq; const xmlSecByte* next; DWORD seqLen; DWORD tlvLen; xmlSecAssert2(params != NULL, -1); xmlSecAssert2(ppP != NULL, -1); xmlSecAssert2(pPLen != NULL, -1); xmlSecAssert2(ppG != NULL, -1); xmlSecAssert2(pGLen != NULL, -1); xmlSecAssert2(ppQ != NULL, -1); xmlSecAssert2(pQLen != NULL, -1); *ppQ = NULL; *pQLen = 0; seq = xmlSecMSCngDerReadTlv(params, end, 0x30 /* SEQUENCE */, &seqLen); if(seq == NULL) { xmlSecInternalError("xmlSecMSCngDerReadTlv(SEQUENCE)", NULL); return(-1); } end = seq + seqLen; *ppP = xmlSecMSCngDerDecodeInteger(seq, end, pPLen); if(*ppP == NULL) { xmlSecInternalError("xmlSecMSCngDerDecodeInteger(P)", NULL); return(-1); } next = xmlSecMSCngDerReadTlv(seq, end, 0x02 /* INTEGER */, &tlvLen); if(next == NULL) { xmlSecInternalError("xmlSecMSCngDerReadTlv(P)", NULL); return(-1); } next += tlvLen; *ppG = xmlSecMSCngDerDecodeInteger(next, end, pGLen); if(*ppG == NULL) { xmlSecInternalError("xmlSecMSCngDerDecodeInteger(G)", NULL); return(-1); } next = xmlSecMSCngDerReadTlv(next, end, 0x02 /* INTEGER */, &tlvLen); if(next == NULL) { xmlSecInternalError("xmlSecMSCngDerReadTlv(G)", NULL); return(-1); } next += tlvLen; if(next < end) { *ppQ = xmlSecMSCngDerDecodeInteger(next, end, pQLen); if(*ppQ == NULL) { xmlSecInternalError("xmlSecMSCngDerDecodeInteger(Q)", NULL); return(-1); } next = xmlSecMSCngDerReadTlv(next, end, 0x02 /* INTEGER */, &tlvLen); if(next == NULL) { xmlSecInternalError("xmlSecMSCngDerReadTlv(Q)", NULL); return(-1); } next += tlvLen; } if(next != end) { xmlSecInvalidSizeError("DH parameters trailing bytes", (xmlSecSize)0, (xmlSecSize)(end - next), NULL); return(-1); } return(0); } /* Copy val (big-endian, possibly shorter than cbKey) right-justified into dest[cbKey] with leading zeros */ int xmlSecMSCngDhBlobCopy(PUCHAR dest, DWORD cbKey, const xmlSecByte* val, xmlSecSize valLen) { xmlSecAssert2(dest != NULL, -1); xmlSecAssert2(cbKey > 0, -1); xmlSecAssert2(val != NULL, -1); xmlSecAssert2(valLen > 0, -1); if(valLen > cbKey) { xmlSecInvalidSizeMoreThanError("DH parameter size", valLen, (xmlSecSize)cbKey, NULL); return(-1); } if(valLen == cbKey) { memcpy(dest, val, cbKey); } else { xmlSecSize pad = cbKey - valLen; memset(dest, 0, pad); memcpy(dest + pad, val, valLen); } return(0); } static int xmlSecMSCngBufferTrimLeadingZeros(xmlSecBufferPtr buffer) { const xmlSecByte* data; xmlSecSize size; xmlSecSize offset = 0; int ret; xmlSecAssert2(buffer != NULL, -1); data = xmlSecBufferGetData(buffer); size = xmlSecBufferGetSize(buffer); xmlSecAssert2(data != NULL, -1); xmlSecAssert2(size > 0, -1); while((offset + 1) < size && data[offset] == 0) { ++offset; } if(offset > 0) { ret = xmlSecBufferRemoveHead(buffer, offset); if(ret < 0) { xmlSecInternalError2("xmlSecBufferRemoveHead", NULL, "size=" XMLSEC_SIZE_FMT, offset); return(-1); } } return(0); } xmlSecKeyDataPtr xmlSecMSCngKeyDataDhRead(xmlSecKeyDataId id, xmlSecKeyValueDhPtr dhValue) { xmlSecKeyDataPtr data = NULL; xmlSecKeyDataPtr res = NULL; xmlSecBuffer blob; int blobInitialized = 0; xmlSecByte* blobData; xmlSecSize pSize, gSize, publicSize; xmlSecSize offset, blobSize; DWORD dwBlobSize; BCRYPT_DH_KEY_BLOB* dhkey; BCRYPT_KEY_HANDLE hKey = NULL; NTSTATUS status; BCRYPT_ALG_HANDLE hAlg = NULL; int ret; xmlSecAssert2(id == xmlSecMSCngKeyDataDhId, NULL); xmlSecAssert2(dhValue != NULL, NULL); xmlSecAssert2(xmlSecBufferGetData(&(dhValue->p)) != NULL, NULL); xmlSecAssert2(xmlSecBufferGetData(&(dhValue->generator)) != NULL, NULL); xmlSecAssert2(xmlSecBufferGetData(&(dhValue->public)) != NULL, NULL); /* dont reverse blobs: both XML and CNG use big-endian */ pSize = xmlSecBufferGetSize(&(dhValue->p)); gSize = xmlSecBufferGetSize(&(dhValue->generator)); publicSize = xmlSecBufferGetSize(&(dhValue->public)); xmlSecAssert2(pSize > 0, NULL); xmlSecAssert2(gSize > 0, NULL); xmlSecAssert2(publicSize > 0, NULL); xmlSecAssert2(gSize <= pSize, NULL); xmlSecAssert2(publicSize <= pSize, NULL); /* BCrypt DH key blob: * BCRYPT_DH_KEY_BLOB header (dwMagic + cbKey) * followed by: P[cbKey] + G[cbKey] + Public[cbKey] * All values are big-endian; shorter values must be right-justified with leading zeros. */ offset = sizeof(BCRYPT_DH_KEY_BLOB); blobSize = offset + pSize * 3; ret = xmlSecBufferInitialize(&blob, blobSize); if (ret < 0) { xmlSecInternalError2("xmlSecBufferInitialize", NULL, "size=" XMLSEC_SIZE_FMT, blobSize); goto done; } blobInitialized = 1; ret = xmlSecBufferSetSize(&blob, blobSize); if (ret < 0) { xmlSecInternalError2("xmlSecBufferSetSize", NULL, "size=" XMLSEC_SIZE_FMT, blobSize); goto done; } memset(xmlSecBufferGetData(&blob), 0, blobSize); blobData = xmlSecBufferGetData(&blob); dhkey = (BCRYPT_DH_KEY_BLOB*)blobData; dhkey->dwMagic = BCRYPT_DH_PUBLIC_MAGIC; XMLSEC_SAFE_CAST_SIZE_TO_UINT(pSize, dhkey->cbKey, goto done, NULL); /* P */ memcpy(blobData + offset, xmlSecBufferGetData(&(dhValue->p)), pSize); offset += pSize; /* G (right-justified with leading zeros for small generators like g=2) */ memcpy(blobData + offset + (pSize - gSize), xmlSecBufferGetData(&(dhValue->generator)), gSize); offset += pSize; /* Public (right-justified) */ memcpy(blobData + offset + (pSize - publicSize), xmlSecBufferGetData(&(dhValue->public)), publicSize); offset += pSize; /* import the key blob */ status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_DH_ALGORITHM, NULL, 0); if (status != STATUS_SUCCESS) { xmlSecMSCngNtError("BCryptOpenAlgorithmProvider", NULL, status); goto done; } XMLSEC_SAFE_CAST_SIZE_TO_UINT(blobSize, dwBlobSize, goto done, NULL); status = BCryptImportKeyPair(hAlg, NULL, BCRYPT_DH_PUBLIC_BLOB, &hKey, blobData, dwBlobSize, 0); if (status != STATUS_SUCCESS) { xmlSecMSCngNtError("BCryptImportKeyPair", NULL, status); goto done; } data = xmlSecKeyDataCreate(id); if (data == NULL) { xmlSecInternalError("xmlSecKeyDataCreate", NULL); goto done; } ret = xmlSecMSCngKeyDataAdoptKey(data, hKey); if (ret < 0) { xmlSecInternalError("xmlSecMSCngKeyDataAdoptKey", xmlSecKeyDataGetName(data)); goto done; } hKey = 0; /* now owned by data */ if(xmlSecBufferGetSize(&(dhValue->q)) > 0) { XMLSEC_SAFE_CAST_SIZE_TO_UINT(xmlSecBufferGetSize(&(dhValue->q)), dwBlobSize, goto done, NULL); ret = xmlSecMSCngKeyDataSetDhQ(data, xmlSecBufferGetData(&(dhValue->q)), dwBlobSize); if(ret < 0) { xmlSecInternalError("xmlSecMSCngKeyDataSetDhQ", NULL); goto done; } } /* success */ res = data; data = NULL; done: if (data != NULL) { xmlSecKeyDataDestroy(data); } if (hAlg != 0) { BCryptCloseAlgorithmProvider(hAlg, 0); } if (hKey != 0) { BCryptDestroyKey(hKey); } if (blobInitialized != 0) { xmlSecBufferFinalize(&blob); } return(res); } int xmlSecMSCngKeyDataDhPubkeyWrite(BCRYPT_KEY_HANDLE pubkey, xmlSecKeyValueDhPtr dhValue) { NTSTATUS status; xmlSecBuffer buf; int bufInitialized = 0; xmlSecByte* bufData; DWORD bufLen = 0; BCRYPT_DH_KEY_BLOB* dhkey; int ret; int res = -1; /* export public key as DH public blob */ status = BCryptExportKey(pubkey, NULL, BCRYPT_DH_PUBLIC_BLOB, NULL, 0, &bufLen, 0); if ((status != STATUS_SUCCESS) || (bufLen <= 0)) { xmlSecMSCngNtError2("BCryptExportKey", NULL, status, "bufLen=%lu", bufLen); goto done; } ret = xmlSecBufferInitialize(&buf, bufLen); if (ret < 0) { xmlSecInternalError2("xmlSecBufferInitialize", NULL, "size=%lu", bufLen); goto done; } bufInitialized = 1; bufData = xmlSecBufferGetData(&buf); xmlSecAssert2(bufData != NULL, -1); status = BCryptExportKey(pubkey, NULL, BCRYPT_DH_PUBLIC_BLOB, bufData, bufLen, &bufLen, 0); if ((status != STATUS_SUCCESS) || (bufLen <= 0)) { xmlSecMSCngNtError2("BCryptExportKey", NULL, status, "bufLen=%lu", bufLen); goto done; } /* parse blob: header + P[cbKey] + G[cbKey] + Public[cbKey] */ if (bufLen < sizeof(BCRYPT_DH_KEY_BLOB)) { xmlSecMSCngNtError2("BCryptExportKey size check", NULL, STATUS_SUCCESS, "bufLen=%lu", bufLen); goto done; } dhkey = (BCRYPT_DH_KEY_BLOB*)bufData; if (dhkey->dwMagic != BCRYPT_DH_PUBLIC_MAGIC) { xmlSecNotImplementedError2("Unexpected DH blob magic: 0x%08lX", (unsigned long)dhkey->dwMagic); goto done; } bufData += sizeof(BCRYPT_DH_KEY_BLOB); bufLen -= (DWORD)sizeof(BCRYPT_DH_KEY_BLOB); if (bufLen != 3 * dhkey->cbKey) { xmlSecMSCngNtError3("BCRYPT_DH_KEY_BLOB size mismatch", NULL, STATUS_SUCCESS, "bufLen=%lu, cbKey=%lu", bufLen, dhkey->cbKey); goto done; } /* dont reverse blobs: both XML and CNG use big-endian */ /* P */ ret = xmlSecBufferSetData(&(dhValue->p), bufData, dhkey->cbKey); if (ret < 0) { xmlSecInternalError2("xmlSecBufferSetData(p)", NULL, "cbKey=%lu", dhkey->cbKey); goto done; } bufData += dhkey->cbKey; /* G */ ret = xmlSecBufferSetData(&(dhValue->generator), bufData, dhkey->cbKey); if (ret < 0) { xmlSecInternalError2("xmlSecBufferSetData(generator)", NULL, "cbKey=%lu", dhkey->cbKey); goto done; } ret = xmlSecMSCngBufferTrimLeadingZeros(&(dhValue->generator)); if(ret < 0) { xmlSecInternalError("xmlSecMSCngBufferTrimLeadingZeros(generator)", NULL); goto done; } bufData += dhkey->cbKey; /* Public */ ret = xmlSecBufferSetData(&(dhValue->public), bufData, dhkey->cbKey); if (ret < 0) { xmlSecInternalError2("xmlSecBufferSetData(public)", NULL, "cbKey=%lu", dhkey->cbKey); goto done; } ret = xmlSecMSCngBufferTrimLeadingZeros(&(dhValue->public)); if(ret < 0) { xmlSecInternalError("xmlSecMSCngBufferTrimLeadingZeros(public)", NULL); goto done; } /* Note: Q (dhValue->q) is not present in the BCrypt DH key blob, left empty */ /* success */ res = 0; done: if (bufInitialized != 0) { xmlSecBufferFinalize(&buf); } return(res); } /** * @brief Loads an X9.42 DH private key (and derives public key) from a PKCS8 DER blob. * @param derData DER-encoded PKCS8 PrivateKeyInfo for an X9.42 DH key. * @param derDataLen length of @p derData. * * Windows CNG does not support PKCS12 loading of X9.42 DH keys, so DER is used. * * @return new key data or NULL on failure. */ xmlSecKeyDataPtr xmlSecMSCngKeyDataDhReadFromPkcs8Der(const xmlSecByte* derData, DWORD derDataLen) { xmlSecKeyDataPtr data = NULL; xmlSecKeyDataPtr res = NULL; CRYPT_PRIVATE_KEY_INFO* pki = NULL; DWORD pkiLen = 0; const xmlSecByte* pP = NULL; DWORD pPLen = 0; const xmlSecByte* pG = NULL; DWORD pGLen = 0; const xmlSecByte* pQ = NULL; DWORD pQLen = 0; const xmlSecByte* pX = NULL; DWORD pXLen = 0; DWORD cbKey = 0; PUCHAR pbPrivBlob = NULL; DWORD cbPrivBlob = 0; BCRYPT_DH_KEY_BLOB* dhPriv = NULL; BCRYPT_KEY_HANDLE hPrivKey = NULL; BCRYPT_KEY_HANDLE hPubKey = NULL; BCRYPT_ALG_HANDLE hAlg = NULL; NTSTATUS status; int ret; xmlSecAssert2(derData != NULL, NULL); xmlSecAssert2(derDataLen > 0, NULL); /* Decode PKCS8 PrivateKeyInfo */ if(!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO, derData, derDataLen, CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, NULL, &pki, &pkiLen)) { xmlSecMSCngLastError("CryptDecodeObjectEx(PKCS8)", NULL); goto done; } if(pki == NULL) { xmlSecInternalError("CryptDecodeObjectEx returned NULL", NULL); goto done; } /* Validate OID — must be X9.42 DH */ if(pki->Algorithm.pszObjId == NULL || strcmp(pki->Algorithm.pszObjId, szOID_X942_DH) != 0) { /* Not a DH key — silently fail so caller can try other formats */ goto done; } if(pki->Algorithm.Parameters.cbData == 0 || pki->Algorithm.Parameters.pbData == NULL) { xmlSecInternalError("DH PKCS8: missing AlgorithmIdentifier.Parameters", NULL); goto done; } /* Parse P and G from AlgorithmIdentifier.Parameters */ ret = xmlSecMSCngDhParseDhParameters( pki->Algorithm.Parameters.pbData, pki->Algorithm.Parameters.cbData, &pP, &pPLen, &pG, &pGLen, &pQ, &pQLen); if(ret < 0) { xmlSecInternalError("xmlSecMSCngDhParseDhParameters", NULL); goto done; } /* Parse X from PrivateKey (inner OCTET STRING contains a DER INTEGER X) */ { const xmlSecByte* inner = pki->PrivateKey.pbData; DWORD innerLen = pki->PrivateKey.cbData; pX = xmlSecMSCngDerDecodeInteger(inner, inner + innerLen, &pXLen); if(pX == NULL) { xmlSecInternalError("DH PKCS8: failed to parse private key INTEGER X", NULL); goto done; } } /* cbKey = max(pPLen, pGLen, pXLen), rounded up to 4-byte boundary — use pPLen */ cbKey = pPLen; /* Build BCRYPT_DH_PRIVATE_BLOB: header + P + G + Public(Y=G^X mod P) + Private(X) * BCRYPT_DH_PRIVATE_BLOB layout: P[cbKey] | G[cbKey] | Public(Y)[cbKey] | Private(X)[cbKey] * We first import with G as Y placeholder, then compute the real Y = G^X mod P using a * self-agreement trick: BCryptSecretAgreement(hPriv, pubWithY=G) computes G^X mod P. */ cbPrivBlob = sizeof(BCRYPT_DH_KEY_BLOB) + cbKey * 4; pbPrivBlob = (PUCHAR)xmlMalloc(cbPrivBlob); if(pbPrivBlob == NULL) { xmlSecMallocError(cbPrivBlob, NULL); goto done; } memset(pbPrivBlob, 0, cbPrivBlob); dhPriv = (BCRYPT_DH_KEY_BLOB*)pbPrivBlob; dhPriv->dwMagic = BCRYPT_DH_PRIVATE_MAGIC; dhPriv->cbKey = cbKey; ret = xmlSecMSCngDhBlobCopy(pbPrivBlob + sizeof(BCRYPT_DH_KEY_BLOB), cbKey, pP, pPLen); /* P */ if(ret < 0) { xmlSecInternalError("xmlSecMSCngDhBlobCopy(P)", NULL); goto done; } ret = xmlSecMSCngDhBlobCopy(pbPrivBlob + sizeof(BCRYPT_DH_KEY_BLOB) + cbKey, cbKey, pG, pGLen); /* G */ if(ret < 0) { xmlSecInternalError("xmlSecMSCngDhBlobCopy(G)", NULL); goto done; } ret = xmlSecMSCngDhBlobCopy(pbPrivBlob + sizeof(BCRYPT_DH_KEY_BLOB) + cbKey * 2, cbKey, pG, pGLen); /* Public(Y) = G placeholder initially */ if(ret < 0) { xmlSecInternalError("xmlSecMSCngDhBlobCopy(Y-placeholder)", NULL); goto done; } ret = xmlSecMSCngDhBlobCopy(pbPrivBlob + sizeof(BCRYPT_DH_KEY_BLOB) + cbKey * 3, cbKey, pX, pXLen); /* Private(X) = actual private exponent */ if(ret < 0) { xmlSecInternalError("xmlSecMSCngDhBlobCopy(X)", NULL); goto done; } status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_DH_ALGORITHM, NULL, 0); if(status != STATUS_SUCCESS) { xmlSecMSCngNtError("BCryptOpenAlgorithmProvider(DH)", NULL, status); goto done; } status = BCryptImportKeyPair(hAlg, NULL, BCRYPT_DH_PRIVATE_BLOB, &hPrivKey, pbPrivBlob, cbPrivBlob, 0); if(status != STATUS_SUCCESS) { xmlSecMSCngNtError("BCryptImportKeyPair(DH priv)", NULL, status); goto done; } /* Compute the real Y = G^X mod P using self-agreement trick: * BCryptSecretAgreement(hPrivKey, pubKeyWithY=G) computes G^X mod P (little-endian). * We reverse the bytes to get big-endian Y, then rebuild the blob with correct Y. */ { BCRYPT_KEY_HANDLE hGPubKey = NULL; BCRYPT_SECRET_HANDLE hSelfSecret = NULL; DWORD cbGPubBlob = sizeof(BCRYPT_DH_KEY_BLOB) + cbKey * 3; DWORD cbY = 0; PUCHAR pbGPubBlob = (PUCHAR)xmlMalloc(cbGPubBlob); if(pbGPubBlob == NULL) { xmlSecMallocError(cbGPubBlob, NULL); goto done; } memset(pbGPubBlob, 0, cbGPubBlob); { BCRYPT_DH_KEY_BLOB* dhGPub = (BCRYPT_DH_KEY_BLOB*)pbGPubBlob; dhGPub->dwMagic = BCRYPT_DH_PUBLIC_MAGIC; dhGPub->cbKey = cbKey; /* P and G same as our key; Y = G (the generator itself) */ memcpy(pbGPubBlob + sizeof(BCRYPT_DH_KEY_BLOB), pbPrivBlob + sizeof(BCRYPT_DH_KEY_BLOB), cbKey); /* P */ memcpy(pbGPubBlob + sizeof(BCRYPT_DH_KEY_BLOB) + cbKey, pbPrivBlob + sizeof(BCRYPT_DH_KEY_BLOB) + cbKey, cbKey); /* G */ memcpy(pbGPubBlob + sizeof(BCRYPT_DH_KEY_BLOB) + cbKey * 2, pbPrivBlob + sizeof(BCRYPT_DH_KEY_BLOB) + cbKey, cbKey); /* Y = G */ } status = BCryptImportKeyPair(hAlg, NULL, BCRYPT_DH_PUBLIC_BLOB, &hGPubKey, pbGPubBlob, cbGPubBlob, 0); xmlFree(pbGPubBlob); if(status != STATUS_SUCCESS) { xmlSecMSCngNtError("BCryptImportKeyPair(DH G pub)", NULL, status); goto done; } status = BCryptSecretAgreement(hPrivKey, hGPubKey, &hSelfSecret, 0); BCryptDestroyKey(hGPubKey); if(status != STATUS_SUCCESS) { xmlSecMSCngNtError("BCryptSecretAgreement(self Y)", NULL, status); goto done; } /* Get Y = G^X mod P (little-endian from BCrypt) */ status = BCryptDeriveKey(hSelfSecret, BCRYPT_KDF_RAW_SECRET, NULL, NULL, 0, &cbY, 0); if((status != STATUS_SUCCESS) || (cbY == 0)) { BCryptDestroySecret(hSelfSecret); xmlSecMSCngNtError("BCryptDeriveKey(Y size)", NULL, status); goto done; } if(cbY > cbKey) { cbY = cbKey; } /* safety */ { PUCHAR pbY = pbPrivBlob + sizeof(BCRYPT_DH_KEY_BLOB) + cbKey * 2; memset(pbY, 0, cbKey); /* derive into Y field of the private blob (right-aligned) */ PUCHAR pbYtmp = pbY + cbKey - cbY; status = BCryptDeriveKey(hSelfSecret, BCRYPT_KDF_RAW_SECRET, NULL, pbYtmp, cbY, &cbY, 0); BCryptDestroySecret(hSelfSecret); if(status != STATUS_SUCCESS) { xmlSecMSCngNtError("BCryptDeriveKey(Y data)", NULL, status); goto done; } /* BCrypt returns little-endian; reverse to big-endian */ xmlSecMSCngReverseBytes(pbYtmp, cbY); } /* Destroy the placeholder private key handle; re-import with correct Y */ BCryptDestroyKey(hPrivKey); hPrivKey = NULL; status = BCryptImportKeyPair(hAlg, NULL, BCRYPT_DH_PRIVATE_BLOB, &hPrivKey, pbPrivBlob, cbPrivBlob, 0); if(status != STATUS_SUCCESS) { xmlSecMSCngNtError("BCryptImportKeyPair(DH priv with Y)", NULL, status); goto done; } } /* Build public key blob using the now-correct Y from slot2 of private blob */ { PUCHAR pbPubBlob = NULL; DWORD cbPubBlob = sizeof(BCRYPT_DH_KEY_BLOB) + cbKey * 3; BCRYPT_DH_KEY_BLOB* dhPub; pbPubBlob = (PUCHAR)xmlMalloc(cbPubBlob); if(pbPubBlob == NULL) { xmlSecMallocError(cbPubBlob, NULL); goto done; } memset(pbPubBlob, 0, cbPubBlob); dhPub = (BCRYPT_DH_KEY_BLOB*)pbPubBlob; dhPub->dwMagic = BCRYPT_DH_PUBLIC_MAGIC; dhPub->cbKey = cbKey; /* copy P, G, Y (slots 0, 1, 2) from the private blob — Y is now correct */ memcpy(pbPubBlob + sizeof(BCRYPT_DH_KEY_BLOB), pbPrivBlob + sizeof(BCRYPT_DH_KEY_BLOB), cbKey * 3); status = BCryptImportKeyPair(hAlg, NULL, BCRYPT_DH_PUBLIC_BLOB, &hPubKey, pbPubBlob, cbPubBlob, 0); xmlFree(pbPubBlob); if(status != STATUS_SUCCESS) { xmlSecMSCngNtError("BCryptImportKeyPair(DH pub)", NULL, status); goto done; } } /* Assemble key data */ data = xmlSecKeyDataCreate(xmlSecMSCngKeyDataDhId); if(data == NULL) { xmlSecInternalError("xmlSecKeyDataCreate(DH)", NULL); goto done; } ret = xmlSecMSCngKeyDataAdoptKey(data, hPubKey); if(ret < 0) { xmlSecInternalError("xmlSecMSCngKeyDataAdoptKey(DH pub)", NULL); goto done; } hPubKey = NULL; /* owned by data */ ret = xmlSecMSCngKeyDataAdoptBCryptPrivKey(data, hPrivKey); if(ret < 0) { xmlSecInternalError("xmlSecMSCngKeyDataAdoptBCryptPrivKey", NULL); goto done; } hPrivKey = NULL; /* owned by data */ ret = xmlSecMSCngKeyDataSetDhQ(data, pQ, pQLen); if(ret < 0) { xmlSecInternalError("xmlSecMSCngKeyDataSetDhQ", NULL); goto done; } res = data; data = NULL; done: if(hPrivKey != NULL) { BCryptDestroyKey(hPrivKey); } if(hPubKey != NULL) { BCryptDestroyKey(hPubKey); } if(hAlg != NULL) { BCryptCloseAlgorithmProvider(hAlg, 0); } if(pbPrivBlob != NULL) { xmlFree(pbPrivBlob); } if(pki != NULL) { LocalFree(pki); } if(data != NULL) { xmlSecKeyDataDestroy(data); } return(res); } #endif /* XMLSEC_NO_DH */