|
| 1 | +/*-----------------------------------------------------------------*\ |
| 2 | + * |
| 3 | + * cmssign.cpp |
| 4 | + * eps2003cspif |
| 5 | + * eps2003csp11-interface |
| 6 | + * |
| 7 | + * MIT - see LICENSE at root directory |
| 8 | + * |
| 9 | + * CREATED: 2022-12-3 12:24 PM |
| 10 | + * AUTHORS: Mohammed Elghamry <elghamry.connect[at]outlook[dot]com> |
| 11 | + * |
| 12 | +\*-----------------------------------------------------------------*/ |
| 13 | + |
| 14 | +#include "pch.h" |
| 15 | + |
| 16 | +#include "eps2003cspif.h" |
| 17 | +#include "cmssign.h" |
| 18 | + |
| 19 | +// REF: https://www.codeproject.com/Articles/1256991/The-AdES-Collection-CAdES-XAdES-PAdES-and-ASiC |
| 20 | + |
| 21 | +// ========================== |
| 22 | +// ====== Declarations ====== |
| 23 | +// ========================== |
| 24 | + |
| 25 | +// ============================ |
| 26 | +// ====== Public Methods ====== |
| 27 | +// ============================ |
| 28 | + |
| 29 | +// ------------------------------------------------------ |
| 30 | +// |
| 31 | +// Create CAdES-BES message for content with one signer. |
| 32 | +// |
| 33 | +// ------------------------------------------------------ |
| 34 | +HRESULT CreateCadesBesSignedMessage(BYTE *pbContent, DWORD cbContent, PCCERT_CONTEXT pCert, BYTE **ppbEncodedBlob, DWORD *pcbEncodedBlob) |
| 35 | +{ |
| 36 | + HRESULT hr{ S_OK }; |
| 37 | + |
| 38 | + asn_enc_rval_t asnEncodingResult{}; |
| 39 | + |
| 40 | + HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hPrivateKey{ 0 }; |
| 41 | + DWORD dwPrivateKeySpec{ 0 }; |
| 42 | + BOOL bShouldCallerFreePrivateKeyHandle{ FALSE }; |
| 43 | + |
| 44 | + FILETIME signingTime{ 0 }; |
| 45 | + BYTE *pbEncodedSigningTime{ nullptr }; |
| 46 | + DWORD cbEncodedSigningTime{ 0 }; |
| 47 | + CRYPT_ATTR_BLOB attrBlobSigningTime{ 0 }; |
| 48 | + |
| 49 | + BYTE *pbCertHash{ nullptr }; |
| 50 | + DWORD cbCertHash{ 0 }; |
| 51 | + |
| 52 | + ESSCertIDv2 essCert{}; |
| 53 | + ESSCertIDv2 *essCerts[]{ &essCert }; |
| 54 | + SigningCertificateV2 signingCert{}; |
| 55 | + std::vector<BYTE> vecEncodedSigningCert{}; |
| 56 | + CRYPT_ATTR_BLOB attrBlobSigningCert{}; |
| 57 | + |
| 58 | + // Holding attributes for the message |
| 59 | + CRYPT_ATTRIBUTE messageAttributes[2]{}; |
| 60 | + |
| 61 | + // Encoded OID of sha256. "2.16.840.1.101.3.4.2.1" |
| 62 | + BYTE oid_sha256_bytes[]{ 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 }; |
| 63 | + OBJECT_IDENTIFIER_t oid_sha256{}; |
| 64 | + AlgorithmIdentifier algid_sha256{}; |
| 65 | + oid_sha256.buf = oid_sha256_bytes; |
| 66 | + oid_sha256.size = sizeof(oid_sha256_bytes); |
| 67 | + algid_sha256.algorithm = oid_sha256; |
| 68 | + |
| 69 | + // Holding information about the certificate |
| 70 | + CERT_BLOB certsEncoded[1]{}; |
| 71 | + certsEncoded[0].cbData = pCert->cbCertEncoded; |
| 72 | + certsEncoded[0].pbData = pCert->pbCertEncoded; |
| 73 | + |
| 74 | + // Holding the information about signers -which is one in our case- |
| 75 | + CMSG_SIGNER_ENCODE_INFO msgSigners[1]{}; |
| 76 | + |
| 77 | + // Holding the information about message signing CMSG_SIGNED |
| 78 | + CMSG_SIGNED_ENCODE_INFO msgInfo{}; |
| 79 | + |
| 80 | + // Here is where we store our encoded message |
| 81 | + BYTE *pbEncodedBlob{ nullptr }; |
| 82 | + DWORD cbEncodedBlob{ 0 }; |
| 83 | + |
| 84 | + // Handle for the opened message |
| 85 | + HCRYPTMSG hMsg{ nullptr }; |
| 86 | + |
| 87 | + // ====== |
| 88 | + // ====== Get Certificate Private Key |
| 89 | + // ====== |
| 90 | + |
| 91 | + if (!CryptAcquireCertificatePrivateKey( |
| 92 | + pCert, |
| 93 | + 0, |
| 94 | + nullptr, |
| 95 | + &hPrivateKey, |
| 96 | + &dwPrivateKeySpec, |
| 97 | + &bShouldCallerFreePrivateKeyHandle)) |
| 98 | + { |
| 99 | + hr = HRESULT_FROM_WIN32(GetLastError()); |
| 100 | + _RPT1(_CRT_WARN, "CryptAcquireCertificatePrivateKey() failed with HRESULT 0x%x\n", hr); |
| 101 | + goto done; |
| 102 | + } |
| 103 | + |
| 104 | + // ====== |
| 105 | + // ====== Create PKCS#9 Signing Time Attribute |
| 106 | + // ====== |
| 107 | + |
| 108 | + GetSystemTimeAsFileTime(&signingTime); |
| 109 | + |
| 110 | + if (!CryptEncodeObjectEx( |
| 111 | + PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, |
| 112 | + szOID_RSA_signingTime, |
| 113 | + &signingTime, |
| 114 | + CRYPT_ENCODE_ALLOC_FLAG, |
| 115 | + nullptr, |
| 116 | + &pbEncodedSigningTime, |
| 117 | + &cbEncodedSigningTime)) |
| 118 | + { |
| 119 | + hr = HRESULT_FROM_WIN32(GetLastError()); |
| 120 | + _RPT1(_CRT_WARN, "CryptEncodeObjectEx() failed with HRESULT 0x%x\n", hr); |
| 121 | + goto done; |
| 122 | + } |
| 123 | + |
| 124 | + attrBlobSigningTime.cbData = cbEncodedSigningTime; |
| 125 | + attrBlobSigningTime.pbData = pbEncodedSigningTime; |
| 126 | + |
| 127 | + messageAttributes[0].pszObjId = const_cast<LPSTR>(szOID_RSA_signingTime); |
| 128 | + messageAttributes[0].cValue = 1; |
| 129 | + messageAttributes[0].rgValue = &attrBlobSigningTime; |
| 130 | + |
| 131 | + // ====== |
| 132 | + // ====== Create SigningCertificateV2 |
| 133 | + // ====== |
| 134 | + |
| 135 | + // === Hash the certificate |
| 136 | + hr = SHA256(pCert->pbCertEncoded, pCert->cbCertEncoded, &pbCertHash, &cbCertHash); |
| 137 | + if (FAILED(hr)) { goto done; } |
| 138 | + |
| 139 | + // === Set the fields on ESSCertIDv2 |
| 140 | + essCert.hashAlgorithm = &algid_sha256; |
| 141 | + essCert.certHash.buf = pbCertHash; |
| 142 | + essCert.certHash.size = cbCertHash; |
| 143 | + |
| 144 | + // === Set the fields on SigningCertificateV2 |
| 145 | + signingCert.certs.list.size = 1; |
| 146 | + signingCert.certs.list.count = 1; |
| 147 | + signingCert.certs.list.array = essCerts; |
| 148 | + |
| 149 | + // === Encode the object |
| 150 | + asnEncodingResult = der_encode( |
| 151 | + &asn_DEF_SigningCertificateV2, |
| 152 | + &signingCert, |
| 153 | + [](const void *buffer, size_t size, void *app_key) |
| 154 | + { |
| 155 | + std::vector<BYTE> *dest = static_cast<std::vector<BYTE> *>(app_key); |
| 156 | + dest->resize(size); |
| 157 | + std::memcpy(dest->data(), buffer, size); |
| 158 | + return 0; |
| 159 | + }, |
| 160 | + &vecEncodedSigningCert); |
| 161 | + if (asnEncodingResult.encoded == -1) |
| 162 | + { |
| 163 | + // Error occurred during encoding |
| 164 | + hr = EPSIF_E_ENCODING_FAILED; |
| 165 | + _RPT0(_CRT_WARN, "der_encode() failed.\n"); |
| 166 | + goto done; |
| 167 | + } |
| 168 | + |
| 169 | + // === Set the attribute |
| 170 | + attrBlobSigningCert.cbData = static_cast<DWORD>(vecEncodedSigningCert.size()); |
| 171 | + attrBlobSigningCert.pbData = vecEncodedSigningCert.data(); |
| 172 | + |
| 173 | + messageAttributes[1].pszObjId = const_cast<LPSTR>("1.2.840.113549.1.9.16.2.47"); // PKCS#9, S/MIME, SigningCertificateV2 |
| 174 | + messageAttributes[1].cValue = 1; |
| 175 | + messageAttributes[1].rgValue = &attrBlobSigningCert; |
| 176 | + |
| 177 | + // ====== |
| 178 | + // ====== Prepare signer info |
| 179 | + // ====== |
| 180 | + |
| 181 | + msgSigners[0].cbSize = sizeof(CMSG_SIGNER_ENCODE_INFO); |
| 182 | + msgSigners[0].pCertInfo = pCert->pCertInfo; |
| 183 | + msgSigners[0].hCryptProv = hPrivateKey; |
| 184 | + msgSigners[0].dwKeySpec = dwPrivateKeySpec; |
| 185 | + msgSigners[0].HashAlgorithm.pszObjId = const_cast<LPSTR>(szOID_NIST_sha256); |
| 186 | + msgSigners[0].cAuthAttr = sizeof(messageAttributes); |
| 187 | + msgSigners[0].rgAuthAttr = messageAttributes; |
| 188 | + |
| 189 | + // ====== |
| 190 | + // ====== Prepare message info |
| 191 | + // ====== |
| 192 | + |
| 193 | + msgInfo.cbSize = sizeof(CMSG_SIGNED_ENCODE_INFO); |
| 194 | + msgInfo.cSigners = sizeof(msgSigners); |
| 195 | + msgInfo.rgSigners = msgSigners; |
| 196 | + msgInfo.cCertEncoded = sizeof(certsEncoded); |
| 197 | + msgInfo.rgCertEncoded = certsEncoded; |
| 198 | + |
| 199 | + // ====== |
| 200 | + // ====== Calculate message size |
| 201 | + // ====== |
| 202 | + |
| 203 | + cbEncodedBlob = CryptMsgCalculateEncodedLength( |
| 204 | + PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, |
| 205 | + CMSG_DETACHED_FLAG, |
| 206 | + CMSG_SIGNED, |
| 207 | + &msgInfo, |
| 208 | + const_cast<LPSTR>(szOID_RSA_digestedData), |
| 209 | + cbContent); |
| 210 | + if (!cbEncodedBlob) |
| 211 | + { |
| 212 | + hr = HRESULT_FROM_WIN32(GetLastError()); |
| 213 | + _RPT1(_CRT_WARN, "CryptMsgCalculateEncodedLength() failed with HRESULT 0x%x\n", hr); |
| 214 | + goto done; |
| 215 | + } |
| 216 | + |
| 217 | + // ====== |
| 218 | + // ====== Allocate memory for encoded message |
| 219 | + // ====== |
| 220 | + |
| 221 | + pbEncodedBlob = new(std::nothrow) BYTE[cbEncodedBlob]; |
| 222 | + if (!pbEncodedBlob) |
| 223 | + { |
| 224 | + hr = E_OUTOFMEMORY; |
| 225 | + goto done; |
| 226 | + } |
| 227 | + |
| 228 | + // ====== |
| 229 | + // ====== Open the message for encoding |
| 230 | + // ====== |
| 231 | + |
| 232 | + hMsg = CryptMsgOpenToEncode( |
| 233 | + PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, |
| 234 | + CMSG_DETACHED_FLAG, |
| 235 | + CMSG_SIGNED, |
| 236 | + &msgInfo, |
| 237 | + const_cast<LPSTR>(szOID_RSA_digestedData), |
| 238 | + nullptr); |
| 239 | + if (!hMsg) |
| 240 | + { |
| 241 | + hr = HRESULT_FROM_WIN32(GetLastError()); |
| 242 | + _RPT1(_CRT_WARN, "CryptMsgOpenToEncode() failed with HRESULT 0x%x\n", hr); |
| 243 | + goto done; |
| 244 | + } |
| 245 | + |
| 246 | + // ====== |
| 247 | + // ====== Update the message with our content |
| 248 | + // ====== |
| 249 | + |
| 250 | + if (!CryptMsgUpdate(hMsg, pbContent, cbContent, TRUE)) |
| 251 | + { |
| 252 | + hr = HRESULT_FROM_WIN32(GetLastError()); |
| 253 | + _RPT1(_CRT_WARN, "CryptMsgUpdate() failed with HRESULT 0x%x\n", hr); |
| 254 | + goto done; |
| 255 | + } |
| 256 | + |
| 257 | + // ====== |
| 258 | + // ====== Get the encoded message |
| 259 | + // ====== |
| 260 | + |
| 261 | + if (!CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, pbEncodedBlob, &cbEncodedBlob)) |
| 262 | + { |
| 263 | + hr = HRESULT_FROM_WIN32(GetLastError()); |
| 264 | + _RPT1(_CRT_WARN, "CryptMsgGetParam() failed with HRESULT 0x%x\n", hr); |
| 265 | + goto done; |
| 266 | + } |
| 267 | + |
| 268 | + // ====== |
| 269 | + // ====== Finally, return the data to the caller |
| 270 | + // ====== |
| 271 | + |
| 272 | + (*ppbEncodedBlob) = pbEncodedBlob; |
| 273 | + (*pcbEncodedBlob) = cbEncodedBlob; |
| 274 | + |
| 275 | +done: |
| 276 | + if (bShouldCallerFreePrivateKeyHandle) |
| 277 | + { |
| 278 | + if (dwPrivateKeySpec == CERT_NCRYPT_KEY_SPEC) |
| 279 | + { |
| 280 | + NCryptFreeObject(hPrivateKey); |
| 281 | + } |
| 282 | + else |
| 283 | + { |
| 284 | + CryptReleaseContext(hPrivateKey, 0); |
| 285 | + } |
| 286 | + } |
| 287 | + |
| 288 | + if (pbEncodedSigningTime) |
| 289 | + { |
| 290 | + LocalFree(pbEncodedSigningTime); |
| 291 | + } |
| 292 | + |
| 293 | + if (pbCertHash) |
| 294 | + { |
| 295 | + delete[] pbCertHash; |
| 296 | + } |
| 297 | + |
| 298 | + if (hMsg) |
| 299 | + { |
| 300 | + CryptMsgClose(hMsg); |
| 301 | + } |
| 302 | + |
| 303 | + return hr; |
| 304 | +} |
0 commit comments