Add automation for code coverage
[platform/core/security/cert-svc.git] / src / vcore / XmlsecAdapter.cpp
1 /*
2  * Copyright (c) 2016-2023 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16 /*
17  * @file        XmlsecAdapter.cpp
18  * @author      Bartlomiej Grzelewski (b.grzelewski@samsung.com)
19  * @author      Sangwan Kwon (sangwan.kwon@samsung.com)
20  * @version     2.1
21  * @brief
22  */
23 #include <cstdlib>
24 #include <cstring>
25 #include <functional>
26 #include <memory>
27
28 #include <libxml/tree.h>
29 #include <libxml/xmlmemory.h>
30 #include <libxml/parser.h>
31
32 #ifndef XMLSEC_NO_XSLT
33 #include <libxslt/xslt.h>
34 #endif
35
36 #include <xmlsec/xmlsec.h>
37 #include <xmlsec/xmltree.h>
38 #include <xmlsec/xmldsig.h>
39 #include <xmlsec/crypto.h>
40 #include <xmlsec/io.h>
41 #include <xmlsec/keyinfo.h>
42 #include <xmlsec/errors.h>
43
44 #include <dpl/assert.h>
45 #include <dpl/log/log.h>
46
47 #include <vcore/XmlsecAdapter.h>
48
49 #define VCORE_ERRORS_BUFFER_SIZE 1024
50
51 namespace {
52
53 struct FileWrapper {
54         FileWrapper(void *argFile, bool argReleased)
55                 : file(argFile)
56                 , released(argReleased)
57         {}
58         void *file;
59         bool released;
60 };
61
62 } // anonymous namespace
63
64 namespace ValidationCore {
65
66 static const std::string DIGEST_MD5 = "md5";
67
68 std::string XmlSec::s_prefixPath;
69
70 int XmlSec::fileMatchCallback(const char *filename)
71 {
72         std::string path = s_prefixPath + filename;
73         return xmlFileMatch(path.c_str());
74 }
75
76 void *XmlSec::fileOpenCallback(const char *filename)
77 {
78         LogDebug("Xmlsec opening:" << filename);
79
80         std::string path = s_prefixPath + filename;
81         return new FileWrapper(xmlFileOpen(path.c_str()), false);
82 }
83
84 int XmlSec::fileReadCallback(void *context,
85                                                          char *buffer,
86                                                          int len)
87 {
88         FileWrapper *fw = static_cast<FileWrapper *>(context);
89
90         if (fw->released)
91                 return 0;
92
93         int output = xmlFileRead(fw->file, buffer, len);
94
95         if (output == 0) {
96                 fw->released = true;
97                 xmlFileClose(fw->file);
98         }
99
100         return output;
101 }
102
103 int XmlSec::fileCloseCallback(void *context)
104 {
105         LogDebug("Xmlsec closing");
106
107         FileWrapper *fw = static_cast<FileWrapper *>(context);
108         int output = 0;
109
110         if (!fw->released)
111                 output = xmlFileClose(fw->file);
112
113         delete fw;
114         return output;
115 }
116
117 void XmlSec::fileExtractPrefix(XmlSecContext &context)
118 {
119         if (!context.workingDirectory.empty()) {
120                 s_prefixPath = context.workingDirectory;
121                 return;
122         }
123
124         s_prefixPath = context.signatureFile;
125         size_t pos = s_prefixPath.rfind('/');
126
127         if (pos == std::string::npos)
128                 s_prefixPath.clear();
129         else
130                 s_prefixPath.erase(pos + 1, std::string::npos);
131 }
132
133 void LogErrorPrint(const char *file,
134                                    int line,
135                                    const char *func,
136                                    const char *errorObject,
137                                    const char *errorSubject,
138                                    int reason,
139                                    const char *msg)
140 {
141         // Get error message from xmlsec.
142         const char *errorMsg = NULL;
143         for (xmlSecSize i = 0; (i < XMLSEC_ERRORS_MAX_NUMBER) &&
144                                                         (xmlSecErrorsGetMsg(i) != NULL); ++i) {
145                 if (xmlSecErrorsGetCode(i) == reason) {
146                         errorMsg = xmlSecErrorsGetMsg(i);
147                         break;
148                 }
149         }
150
151         // Make full error message.
152         char buff[VCORE_ERRORS_BUFFER_SIZE];
153         snprintf(buff,
154                         sizeof(buff),
155                         "[%s:%d] %s(): obj=%s, subj=%s, error=%d:%s:%s\n",
156                         file,
157                         line,
158                         func,
159                         (errorObject != NULL) ? errorObject : "",
160                         (errorSubject != NULL) ? errorSubject : "",
161                         reason,
162                         (errorMsg != NULL) ? errorMsg : "",
163                         (msg != NULL) ? msg : "");
164         buff[sizeof(buff) - 1] = '\0';
165
166         LogError(buff);
167 }
168
169 XmlSec::XmlSec() :
170         m_mode(ValidateMode::NORMAL),
171         m_pList(nullptr)
172 {
173         LIBXML_TEST_VERSION
174         xmlLoadExtDtdDefaultValue = XML_DETECT_IDS | XML_COMPLETE_ATTRS;
175         xmlSubstituteEntitiesDefault(1);
176 #ifndef XMLSEC_NO_XSLT
177         xmlIndentTreeOutput = 1;
178 #endif
179
180         if (xmlSecInit() < 0)
181                 ThrowMsg(Exception::InternalError, "Xmlsec initialization failed.");
182
183         if (xmlSecCheckVersion() != 1) {
184                 xmlSecShutdown();
185                 ThrowMsg(Exception::InternalError,
186                                  "Loaded xmlsec library version is not compatible.");
187         }
188
189 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
190
191         if (xmlSecCryptoDLLoadLibrary(BAD_CAST XMLSEC_CRYPTO) < 0) {
192                 xmlSecShutdown();
193                 LogError(
194                         "Error: unable to load default xmlsec-crypto library. Make sure "
195                         "that you have it installed and check shared libraries path "
196                         "(LD_LIBRARY_PATH) envornment variable.");
197                 ThrowMsg(Exception::InternalError,
198                                  "Unable to load default xmlsec-crypto library.");
199         }
200
201 #endif
202
203         if (xmlSecCryptoAppInit(nullptr) < 0) {
204                 xmlSecShutdown();
205                 ThrowMsg(Exception::InternalError, "Crypto initialization failed.");
206         }
207
208         if (xmlSecCryptoInit() < 0) {
209                 xmlSecCryptoAppShutdown();
210                 xmlSecShutdown();
211                 ThrowMsg(Exception::InternalError,
212                                  "Xmlsec-crypto initialization failed.");
213         }
214 }
215
216 XmlSec::~XmlSec()
217 {
218         xmlSecCryptoShutdown();
219         xmlSecCryptoAppShutdown();
220         xmlSecShutdown();
221 #ifndef XMLSEC_NO_XSLT
222         xsltCleanupGlobals();
223 #endif
224         s_prefixPath.clear();
225 }
226
227 void XmlSec::validateFile(XmlSecContext &context, xmlSecKeysMngrPtr mngrPtr)
228 {
229         fileExtractPrefix(context);
230         LogDebug("Prefix:" << s_prefixPath);
231         xmlSecIOCleanupCallbacks();
232         if (xmlSecIORegisterCallbacks(
233                 fileMatchCallback,
234                 fileOpenCallback,
235                 fileReadCallback,
236                 fileCloseCallback) < 0)
237                 ThrowMsg(Exception::InternalError,
238                                  "Error in xmlSecIORegisterCallbacks");
239
240         std::unique_ptr<xmlDoc, std::function<void(xmlDocPtr)>> docPtr(
241                 xmlParseFile(context.signatureFile.c_str()), xmlFreeDoc);
242
243         if (!docPtr || xmlDocGetRootElement(docPtr.get()) == nullptr)
244                 ThrowMsg(Exception::InvalidFormat,
245                                  "Unable to parse sig xml file: " << context.signatureFile);
246
247         xmlNodePtr node = xmlSecFindNode(
248                                                   xmlDocGetRootElement(docPtr.get()),
249                                                   xmlSecNodeSignature,
250                                                   xmlSecDSigNs);
251
252         if (node == nullptr)
253                 ThrowMsg(Exception::InvalidFormat,
254                                  "Start node not found in " << context.signatureFile);
255
256         std::unique_ptr<xmlSecDSigCtx, std::function<void(xmlSecDSigCtxPtr)>> dsigCtx(
257                 xmlSecDSigCtxCreate(mngrPtr),
258                 [](xmlSecDSigCtxPtr dsigCtx) {
259                         xmlSecProxyCtxDestroy(dsigCtx->skipReferences);
260                         xmlSecProxyCtxDestroy(dsigCtx->checkReferences);
261                         xmlSecDSigCtxDestroy(dsigCtx);
262         });
263
264         if (!dsigCtx)
265                 ThrowMsg(Exception::OutOfMemory, "Failed to create signature context.");
266
267         if (context.allowBrokenChain)
268                 dsigCtx->keyInfoReadCtx.flags |= XMLSEC_KEYINFO_FLAGS_ALLOW_BROKEN_CHAIN;
269
270         if (context.validationTime) {
271                 LogDebug("Setting validation time.");
272                 dsigCtx->keyInfoReadCtx.certsVerificationTime = context.validationTime;
273         }
274
275         // Set proxy data to dsigCtx
276         if (context.isProxyMode && !context.proxySet.empty()) {
277                 LogDebug("Set proxy data to xmlsec1 handle.");
278                 for (auto &data : context.proxySet) {
279                         if (!strcmp(data.c_str(), "#prop"))
280                                 continue;
281
282                         if(xmlSecProxyCtxAdd(&(dsigCtx.get()->skipReferences),
283                                                                  reinterpret_cast<const xmlChar *>(data.c_str())))
284                                 ThrowMsg(Exception::InternalError, "Failed to add proxy data.");
285                         else
286                                 LogDebug("Add [" << data << "] to proxy.");
287
288                 }
289         }
290
291         int res;
292
293         switch (m_mode) {
294         case ValidateMode::NORMAL: {
295                 if (context.isProxyMode)
296                         dsigCtx.get()->flags |= XMLSEC_DSIG_FLAGS_SKIP_PROXY;
297
298                 res = xmlSecDSigCtxVerify(dsigCtx.get(), node);
299                 break;
300         }
301
302         case ValidateMode::NO_HASH:
303                 dsigCtx.get()->flags |= XMLSEC_DSIG_FLAGS_IGNORE_REFERENCES;
304                 res = xmlSecDSigCtxVerify(dsigCtx.get(), node);
305                 break;
306
307         case ValidateMode::PARTIAL_HASH: {
308                 if (context.isProxyMode)
309                         dsigCtx.get()->flags |= XMLSEC_DSIG_FLAGS_SKIP_PROXY;
310
311                 dsigCtx.get()->flags |= XMLSEC_DSIG_FLAGS_CHECK_PROXY;
312                 for (auto &uri : *m_pList) {
313                         if(xmlSecProxyCtxAdd(&(dsigCtx.get()->checkReferences),
314                                                                  reinterpret_cast<const xmlChar *>(uri.c_str())))
315                                 ThrowMsg(Exception::InternalError, "PARTIAL_HASH mode failed.");
316                 }
317                 res = xmlSecDSigCtxVerify(dsigCtx.get(), node);
318                 break;
319         }
320
321         default:
322                 ThrowMsg(Exception::InternalError, "ValidateMode is invalid");
323         }
324
325         if (res != 0)
326                 ThrowMsg(Exception::InvalidSig, "Signature verify error.");
327
328         if (dsigCtx->keyInfoReadCtx.flags2 & XMLSEC_KEYINFO_ERROR_FLAGS_BROKEN_CHAIN) {
329                 LogWarning("Signature contains broken chain!");
330                 context.errorBrokenChain = true;
331         }
332
333         if (dsigCtx->status != xmlSecDSigStatusSucceeded)
334                 ThrowMsg(Exception::InvalidSig, "Signature status is not succedded.");
335
336         // Set references for reverse reference check by ReferenceValidator
337         if (context.isProxyMode)
338                 for (auto &proxy : context.proxySet)
339                         context.referenceSet.insert(proxy);
340
341         xmlSecSize refSize = xmlSecPtrListGetSize(&(dsigCtx->signedInfoReferences));
342         for (xmlSecSize i = 0; i < refSize; ++i) {
343                 xmlSecDSigReferenceCtxPtr dsigRefCtx = static_cast<xmlSecDSigReferenceCtxPtr>(
344                                 xmlSecPtrListGetItem(&(dsigCtx->signedInfoReferences), i));
345
346                 if (!dsigRefCtx || !dsigRefCtx->uri)
347                         continue;
348
349                 if (dsigRefCtx->digestMethod
350                                 && dsigRefCtx->digestMethod->id
351                                 && dsigRefCtx->digestMethod->id->name) {
352                         auto digest = reinterpret_cast<const char *>(
353                                                           dsigRefCtx->digestMethod->id->name);
354
355                         if (DIGEST_MD5.compare(digest) == 0)
356                                 ThrowMsg(Exception::InvalidFormat,
357                                                  "MD5 digest method used! Please use sha");
358                 }
359
360                 context.referenceSet.emplace(reinterpret_cast<char *>(dsigRefCtx->uri));
361                 if (context.isProxyMode)
362                         context.proxySet.emplace(reinterpret_cast<char *>(dsigRefCtx->uri));
363         }
364 }
365
366 void XmlSec::loadDERCertificateMemory(XmlSecContext &context, xmlSecKeysMngrPtr mngrPtr)
367 {
368         std::string derCert;
369
370         try {
371                 derCert = context.certificatePtr->getDER();
372         } catch (Certificate::Exception::Base &e) {
373                 ThrowMsg(Exception::InternalError,
374                                  "Failed during x509 conversion to der format: " << e.DumpToString());
375         }
376
377         if (xmlSecCryptoAppKeysMngrCertLoadMemory(
378                                 mngrPtr,
379                                 reinterpret_cast<const xmlSecByte *>(derCert.data()),
380                                 static_cast<xmlSecSize>(derCert.length()),
381                                 xmlSecKeyDataFormatDer,
382                                 xmlSecKeyDataTypeTrusted) < 0)
383                 ThrowMsg(Exception::InternalError, "Failed to load der cert from memory.");
384 }
385
386 void XmlSec::loadPEMCertificateFile(XmlSecContext &context, xmlSecKeysMngrPtr mngrPtr)
387 {
388         if (xmlSecCryptoAppKeysMngrCertLoad(
389                                 mngrPtr,
390                                 context.certificatePath.c_str(),
391                                 xmlSecKeyDataFormatPem,
392                                 xmlSecKeyDataTypeTrusted) < 0)
393                 ThrowMsg(Exception::InternalError, "Failed to load PEM cert from file.");
394 }
395
396 void XmlSec::validateInternal(XmlSecContext &context)
397 {
398         LogDebug("Start to validate.");
399         Assert(!context.signatureFile.empty());
400         Assert(!!context.certificatePtr || !context.certificatePath.empty());
401         xmlSecErrorsSetCallback(LogErrorPrint);
402
403         std::unique_ptr<xmlSecKeysMngr, std::function<void(xmlSecKeysMngrPtr)>>
404                 mngrPtr(xmlSecKeysMngrCreate(), xmlSecKeysMngrDestroy);
405
406         if (!mngrPtr)
407                 ThrowMsg(Exception::InternalError, "Failed to create keys manager.");
408
409         if (xmlSecCryptoAppDefaultKeysMngrInit(mngrPtr.get()) < 0)
410                 ThrowMsg(Exception::InternalError, "Failed to initialize keys manager.");
411
412         context.referenceSet.clear();
413
414         if (!!context.certificatePtr)
415                 loadDERCertificateMemory(context, mngrPtr.get());
416
417         if (!context.certificatePath.empty())
418                 loadPEMCertificateFile(context, mngrPtr.get());
419
420         validateFile(context, mngrPtr.get());
421 }
422
423 void XmlSec::validate(XmlSecContext &context)
424 {
425         m_mode = ValidateMode::NORMAL;
426         validateInternal(context);
427 }
428
429 void XmlSec::validateNoHash(XmlSecContext &context)
430 {
431         m_mode = ValidateMode::NO_HASH;
432         validateInternal(context);
433 }
434
435 void XmlSec::validatePartialHash(XmlSecContext &context, const std::list<std::string> &targetUri)
436 {
437         m_mode = ValidateMode::PARTIAL_HASH;
438         m_pList = &targetUri;
439         validateInternal(context);
440 }
441
442 } // namespace ValidationCore