Refactor SignatureValidator and reduce interface headers
[platform/core/security/cert-svc.git] / vcore / src / vcore / SignatureValidator.cpp
1 /*
2  * Copyright (c) 2011 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        SignatureValidator.cpp
18  * @author      Bartlomiej Grzelewski (b.grzelewski@samsung.com)
19  * @version     1.0
20  * @brief       Implementatin of tizen signature validation protocol.
21  */
22
23 #include <vcore/SignatureValidator.h>
24 #include <vcore/CertificateCollection.h>
25 #include <vcore/Certificate.h>
26 #include <vcore/ReferenceValidator.h>
27 #include <vcore/ValidatorFactories.h>
28 #include <vcore/XmlsecAdapter.h>
29 #include <vcore/SignatureReader.h>
30 #include <vcore/SignatureFinder.h>
31
32 #include <dpl/log/log.h>
33
34 namespace {
35
36 const std::string TOKEN_ROLE_AUTHOR_URI =
37         "http://www.w3.org/ns/widgets-digsig#role-author";
38 const std::string TOKEN_ROLE_DISTRIBUTOR_URI =
39         "http://www.w3.org/ns/widgets-digsig#role-distributor";
40 const std::string TOKEN_PROFILE_URI =
41         "http://www.w3.org/ns/widgets-digsig#profile";
42
43 static tm _ASN1_GetTimeT(ASN1_TIME *time)
44 {
45         struct tm t;
46         const char* str = (const char *)time->data;
47         size_t i = 0;
48
49         memset(&t, 0, sizeof(t));
50
51         if (time->type == V_ASN1_UTCTIME) {
52                 /* two digit year */
53                 t.tm_year = (str[i] - '0') * 10 + (str[i + 1] - '0');
54                 i += 2;
55                 if (t.tm_year < 70)
56                         t.tm_year += 100;
57         } else if (time->type == V_ASN1_GENERALIZEDTIME) {
58                 /* four digit year */
59                 t.tm_year =
60                         (str[i] - '0') * 1000
61                         + (str[i + 1] - '0') * 100
62                         + (str[i + 2] - '0') * 10
63                         + (str[i + 3] - '0');
64                 i += 4;
65                 t.tm_year -= 1900;
66         }
67
68         t.tm_mon  = (str[i]     - '0') * 10 + (str[i + 1] - '0') - 1; // -1 since January is 0 not 1.
69         t.tm_mday = (str[i + 2] - '0') * 10 + (str[i + 3] - '0');
70         t.tm_hour = (str[i + 4] - '0') * 10 + (str[i + 5] - '0');
71         t.tm_min  = (str[i + 6] - '0') * 10 + (str[i + 7] - '0');
72         t.tm_sec  = (str[i + 8] - '0') * 10 + (str[i + 9] - '0');
73
74         /* Note: we did not adjust the time based on time zone information */
75         return t;
76 }
77
78 static bool checkRoleURI(const ValidationCore::SignatureData &data)
79 {
80         std::string roleURI = data.getRoleURI();
81
82         if (roleURI.empty()) {
83                 LogWarning("URI attribute in Role tag couldn't be empty.");
84                 return false;
85         }
86
87         if (roleURI != TOKEN_ROLE_AUTHOR_URI && data.isAuthorSignature()) {
88                 LogWarning("URI attribute in Role tag does not "
89                         "match with signature filename.");
90                 return false;
91         }
92
93         if (roleURI != TOKEN_ROLE_DISTRIBUTOR_URI && !data.isAuthorSignature()) {
94                 LogWarning("URI attribute in Role tag does not "
95                         "match with signature filename.");
96                 return false;
97         }
98         return true;
99 }
100
101 static bool checkProfileURI(const ValidationCore::SignatureData &data)
102 {
103         if (TOKEN_PROFILE_URI != data.getProfileURI()) {
104                 LogWarning("Profile tag contains unsupported value "
105                         "in URI attribute " << data.getProfileURI());
106                 return false;
107         }
108         return true;
109 }
110
111 static bool checkObjectReferences(const ValidationCore::SignatureData &data)
112 {
113         ValidationCore::ObjectList objectList = data.getObjectList();
114         ValidationCore::ObjectList::const_iterator iter;
115         for (iter = objectList.begin(); iter != objectList.end(); ++iter) {
116                 if (!data.containObjectReference(*iter)) {
117                         LogWarning("Signature does not contain reference for object " << *iter);
118                         return false;
119                 }
120         }
121         return true;
122 }
123
124 static struct tm getMidTime(const struct tm &tb, const struct tm &ta)
125 {
126         struct tm tMid;
127         memset(&tMid, 0, sizeof(tMid));
128
129         LogDebug("Certificate's notBeforeTime : Year["
130                 << (tb.tm_year + 1900)
131                 << "] Month[" << (tb.tm_mon + 1)
132                 << "] Day[" << tb.tm_mday << "]  ");
133
134         LogDebug("Certificate's notAfterTime : Year["
135                 << (ta.tm_year + 1900)
136                 << "] Month[" << (ta.tm_mon + 1)
137                 << "] Day[" << ta.tm_mday << "]  ");
138
139         int year = (ta.tm_year - tb.tm_year) / 4;
140
141         if (year == 0) {
142                 tMid.tm_year = tb.tm_year;
143                 tMid.tm_mon = tb.tm_mon + 1;
144                 tMid.tm_mday = tb.tm_mday;
145
146                 if (tMid.tm_mon == 12) {
147                         tMid.tm_year = ta.tm_year;
148                         tMid.tm_mon = ta.tm_mon - 1;
149                         tMid.tm_mday = ta.tm_mday;
150
151                         if (tMid.tm_mon < 0) {
152                                 tMid.tm_year = ta.tm_year;
153                                 tMid.tm_mon = ta.tm_mon;
154                                 tMid.tm_mday = ta.tm_mday - 1;
155
156                                 if (tMid.tm_mday == 0) {
157                                         tMid.tm_year = tb.tm_year;
158                                         tMid.tm_mon = tb.tm_mon;
159                                         tMid.tm_mday = tb.tm_mday + 1;
160                                 }
161                         }
162                 }
163         } else {
164                 tMid.tm_year = tb.tm_year + year;
165                 tMid.tm_mon = (tb.tm_mon + ta.tm_mon) / 2;
166                 tMid.tm_mday = (tb.tm_mday + ta.tm_mday) / 2;
167         }
168
169         LogDebug("cmp cert with validation time. Year["
170                 << (tMid.tm_year + 1900)
171                 << "] Month[" << (tMid.tm_mon + 1)
172                 << "] Day[" << tMid.tm_mday << "]  ");
173
174         return tMid;
175 }
176
177 } // namespace anonymouse
178
179
180
181 namespace ValidationCore {
182
183 /*
184  *  Prepare to check / checklist. parse xml and save info to signature data.
185  *
186  *  [in]  fileInfo : signature file information to check. file path should be absolute path
187  *                   which is made by SignatureFinder.
188  *  [out] outData  : signature data for validating and will be finally returned to client.
189  */
190 int prepareToCheck(const SignatureFileInfo &fileInfo, SignatureData &outData)
191 {
192         outData = SignatureData(fileInfo.getFileName(), fileInfo.getFileNumber());
193
194         try {
195                 SignatureReader xml;
196                 xml.initialize(outData, SIGNATURE_SCHEMA_PATH);
197                 xml.read(outData);
198         } catch (...) {
199                 LogError("Failed to parse signature file by signature reader.");
200                 return -1;
201         }
202
203         return 0;
204 }
205
206 /*
207  *  Same logic (check, checkList) is functionalized here.
208  *
209  *  [in]  checkOcsp : If on, check ocsp.
210  *  [out] disregard : distributor signature disregard flag.
211  *  [out] context   : xml sec for validating.
212  *  [out] data      : signature data for validationg and will be finally returned to client.
213  */
214 static SignatureValidator::Result checkInternal(
215         bool checkOcsp,
216         bool &disregard,
217         XmlSec::XmlSecContext &context,
218         SignatureData &data)
219 {
220         // TODO: impl ocsp check
221         (void) checkOcsp;
222
223         if (!checkRoleURI(data) || !checkProfileURI(data))
224                 return SignatureValidator::SIGNATURE_INVALID;
225
226         CertificateCollection collection;
227         collection.load(data.getCertList());
228
229         if (!collection.sort() || collection.empty() || !collection.completeCertificateChain()) {
230                 LogWarning("Certificates do not form valid chain.");
231                 return SignatureValidator::SIGNATURE_INVALID;
232         }
233
234         CertificateList sortedCertificateList = collection.getChain();
235         CertificatePtr root = sortedCertificateList.back();
236
237         // Is Root CA certificate trusted?
238         CertStoreId::Set storeIdSet = createCertificateIdentifier().find(root);
239
240         LogDebug("root certificate from " << storeIdSet.typeToString() << " domain");
241         if (data.isAuthorSignature()) {
242                 if (!storeIdSet.contains(CertStoreId::TIZEN_DEVELOPER)) {
243                         LogWarning("author-signature.xml has got unrecognized Root CA "
244                                 "certificate. Signature will be disregarded.");
245                         disregard = true;
246                 }
247         } else {
248                 LogDebug("signaturefile name = " << data.getSignatureFileName());
249                 if (storeIdSet.contains(CertStoreId::TIZEN_DEVELOPER)) {
250                         LogError("distributor has author level siganture! Signature will be disregarded.");
251                         return SignatureValidator::SIGNATURE_INVALID;
252                 }
253
254                 if (data.getSignatureNumber() == 1 && !storeIdSet.isContainsVis()) {
255                         LogWarning("signature1.xml has got unrecognized Root CA "
256                                 "certificate. Signature will be disregarded.");
257                         disregard = true;
258                 }
259         }
260
261         data.setStorageType(storeIdSet);
262         data.setSortedCertificateList(sortedCertificateList);
263
264         /*
265          * We add only Root CA certificate because the rest
266          * of certificates are present in signature files ;-)
267          */
268         context.signatureFile = data.getSignatureFileName();
269         context.certificatePtr = root;
270
271         /* certificate time check */
272         ASN1_TIME* notAfterTime = data.getEndEntityCertificatePtr()->getNotAfterTime();
273         ASN1_TIME* notBeforeTime = data.getEndEntityCertificatePtr()->getNotBeforeTime();
274
275         time_t nowTime = time(NULL);
276
277         if (X509_cmp_time(notBeforeTime, &nowTime) > 0  || X509_cmp_time(notAfterTime, &nowTime) < 0) {
278                 if (storeIdSet.contains(CertStoreId::TIZEN_TEST) || storeIdSet.contains(CertStoreId::TIZEN_VERIFY)) {
279                         LogError("TIZEN_VERIFY : check certificate Time : FALSE");
280                         return SignatureValidator::SIGNATURE_INVALID;
281                 }
282
283                 struct tm tMid = getMidTime(_ASN1_GetTimeT(notBeforeTime), _ASN1_GetTimeT(notAfterTime));
284
285                 context.validationTime = mktime(&tMid);
286         }
287
288         return SignatureValidator::SIGNATURE_VERIFIED;
289 }
290
291 SignatureValidator::Result SignatureValidator::check(
292         const SignatureFileInfo &fileInfo,
293         const std::string &widgetContentPath,
294         bool checkOcsp,
295         bool checkReferences,
296         SignatureData &outData)
297 {
298         if (prepareToCheck(fileInfo, outData)) {
299                 LogError("Failed to prepare to check.");
300                 return SIGNATURE_INVALID;
301         }
302
303         bool disregard = false;
304
305         try {
306                 XmlSec::XmlSecContext context;
307                 Result result = checkInternal(checkOcsp, disregard, context, outData);
308                 if (result != SIGNATURE_VERIFIED)
309                         return result;
310
311                 if (!outData.isAuthorSignature()) {
312                         if (XmlSec::NO_ERROR != XmlSecSingleton::Instance().validate(&context)) {
313                                 LogWarning("Installation break - invalid package!");
314                                 return SIGNATURE_INVALID;
315                         }
316
317                         outData.setReference(context.referenceSet);
318                         if (!checkObjectReferences(outData)) {
319                                 LogWarning("Failed to check Object References");
320                                 return SIGNATURE_INVALID;
321                         }
322
323                         if (checkReferences) {
324                                 ReferenceValidator fileValidator(widgetContentPath);
325                                 if (ReferenceValidator::NO_ERROR != fileValidator.checkReferences(outData)) {
326                                         LogWarning("Invalid package - file references broken");
327                                         return SIGNATURE_INVALID;
328                                 }
329                         }
330                 }
331         } catch (const CertificateCollection::Exception::Base &e) {
332                 LogError("CertificateCollection exception : " << e.DumpToString());
333                 return SIGNATURE_INVALID;
334         } catch (const XmlSec::Exception::Base &e) {
335                 LogError("XmlSec exception : " << e.DumpToString());
336                 return SIGNATURE_INVALID;
337         } catch (...) {
338                 LogError("Unknown exception in SignatureValidator::check");
339                 return SIGNATURE_INVALID;
340         }
341
342         return disregard ? SIGNATURE_DISREGARD : SIGNATURE_VERIFIED;
343 }
344
345 SignatureValidator::Result SignatureValidator::checkList(
346         const SignatureFileInfo &fileInfo,
347         const std::string &widgetContentPath,
348         const std::list<std::string> &uriList,
349         bool checkOcsp,
350         bool checkReferences,
351         SignatureData &outData)
352 {
353         if (prepareToCheck(fileInfo, outData)) {
354                 LogError("Failed to prepare to check.");
355                 return SIGNATURE_INVALID;
356         }
357
358         bool disregard = false;
359         try {
360                 XmlSec::XmlSecContext context;
361                 Result result = checkInternal(checkOcsp, disregard, context, outData);
362                 if (result != SIGNATURE_VERIFIED)
363                         return result;
364
365                 if (uriList.size() == 0) {
366                         if (XmlSec::NO_ERROR != XmlSecSingleton::Instance().validateNoHash(&context)) {
367                                 LogWarning("Installation break - invalid package! >> validateNoHash");
368                                 return SIGNATURE_INVALID;
369                         }
370                 } else {
371                         XmlSecSingleton::Instance().setPartialHashList(uriList);
372                         if (XmlSec::NO_ERROR != XmlSecSingleton::Instance().validatePartialHash(&context)) {
373                                 LogWarning("Installation break - invalid package! >> validatePartialHash");
374                                 return SIGNATURE_INVALID;
375                         }
376                 }
377
378                 outData.setReference(context.referenceSet);
379                 /*
380                 if (!checkObjectReferences(outData)) {
381                         LogWarning("Failed to check Object References");
382                         return SIGNATURE_INVALID;
383                 }
384                 */
385
386                 if (checkReferences) {
387                         ReferenceValidator fileValidator(widgetContentPath);
388                         if (ReferenceValidator::NO_ERROR != fileValidator.checkReferences(outData)) {
389                                 LogWarning("Invalid package - file references broken");
390                                 return SIGNATURE_INVALID;
391                         }
392                 }
393         } catch (const CertificateCollection::Exception::Base &e) {
394                 LogError("CertificateCollection exception : " << e.DumpToString());
395                 return SIGNATURE_INVALID;
396         } catch (const XmlSec::Exception::Base &e) {
397                 LogError("XmlSec exception : " << e.DumpToString());
398                 return SIGNATURE_INVALID;
399         } catch (...) {
400                 LogError("Unknown exception in SignatureValidator::checkList");
401                 return SIGNATURE_INVALID;
402         }
403
404         return disregard ? SIGNATURE_DISREGARD : SIGNATURE_VERIFIED;
405 }
406
407
408 } // namespace ValidationCore
409