6602e1734ebd32acca5383de9f1728e7af8170ec
[platform/core/security/cert-svc.git] / vcore / src / vcore / CRLImpl.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  * @author      Piotr Marcinkiewicz(p.marcinkiew@samsung.com)
18  * @version     0.2
19  * @file        CRLImpl.cpp
20  * @brief       Routines for certificate validation over CRL
21  */
22
23 #include <vcore/CRL.h>
24 #include <vcore/CRLImpl.h>
25
26 #include <set>
27 #include <algorithm>
28
29 #include <openssl/err.h>
30 #include <openssl/objects.h>
31 #include <openssl/ocsp.h>
32 #include <openssl/pem.h>
33 #include <openssl/x509v3.h>
34
35 #include <dpl/log/wrt_log.h>
36 #include <dpl/assert.h>
37 #include <dpl/db/orm.h>
38 #include <dpl/foreach.h>
39
40 #include <vcore/Base64.h>
41 #include <vcore/Certificate.h>
42 #include <vcore/SoupMessageSendSync.h>
43 #include <vcore/CRLCacheInterface.h>
44
45 namespace {
46 const char *CRL_LOOKUP_DIR = "/usr/share/ca-certificates/wac";
47 } //anonymous namespace
48
49 namespace ValidationCore {
50
51 CRL::StringList CRLImpl::getCrlUris(const CertificatePtr &argCert)
52 {
53     CRL::StringList result = argCert->getCrlUris();
54
55     if (!result.empty()) {
56         return result;
57     }
58     WrtLogI("No distribution points found. Getting from CA cert.");
59     X509_STORE_CTX *ctx = createContext(argCert);
60     X509_OBJECT obj;
61
62     //Try to get distribution points from CA certificate
63     int retVal = X509_STORE_get_by_subject(ctx, X509_LU_X509,
64                                            X509_get_issuer_name(argCert->
65                                                                     getX509()),
66                                            &obj);
67     X509_STORE_CTX_free(ctx);
68     if (0 >= retVal) {
69         WrtLogE("No dedicated CA certificate available");
70         return result;
71     }
72     CertificatePtr caCert(new Certificate(obj.data.x509));
73     X509_OBJECT_free_contents(&obj);
74     return caCert->getCrlUris();
75 }
76
77 CRLImpl::CRLImpl(CRLCacheInterface *ptr)
78   : m_crlCache(ptr)
79 {
80     Assert(m_crlCache != NULL);
81
82     WrtLogI("CRL storage initialization.");
83     m_store = X509_STORE_new();
84     if (!m_store)
85         VcoreThrowMsg(CRLException::StorageError,
86                       "impossible to create new store");
87
88     m_lookup = X509_STORE_add_lookup(m_store, X509_LOOKUP_hash_dir());
89     if (!m_lookup) {
90         cleanup();
91         VcoreThrowMsg(CRLException::StorageError,
92                       "impossible to add hash dir lookup");
93     }
94     // Add hash dir pathname for CRL checks
95     bool retVal = X509_LOOKUP_add_dir(m_lookup, CRL_LOOKUP_DIR, X509_FILETYPE_PEM) == 1;
96     retVal &= X509_LOOKUP_add_dir(m_lookup, CRL_LOOKUP_DIR, X509_FILETYPE_ASN1) == 1;
97     if (!retVal) {
98         cleanup();
99         VcoreThrowMsg(CRLException::StorageError,
100                       "Failed to add lookup dir for PEM files");
101     }
102     WrtLogI("CRL storage initialization complete.");
103 }
104
105 CRLImpl::~CRLImpl()
106 {
107     cleanup();
108     delete m_crlCache;
109 }
110
111 void CRLImpl::cleanup()
112 {
113     WrtLogI("Free CRL storage");
114     // STORE is responsible for LOOKUP release
115     //    X509_LOOKUP_free(m_lookup);
116     X509_STORE_free(m_store);
117 }
118
119 CRL::RevocationStatus CRLImpl::checkCertificate(const CertificatePtr &argCert)
120 {
121     CRL::RevocationStatus retStatus = {false, false};
122     int retVal = 0;
123     CRL::StringList crlUris = getCrlUris(argCert);
124     FOREACH(it, crlUris) {
125         CRLDataPtr crl = getCRL(*it);
126         if (!crl) {
127             WrtLogD("CRL not found for URI: %s", (*it).c_str());
128             continue;
129         }
130         X509_CRL *crlInternal = convertToInternal(crl);
131
132         //Check date
133         if (X509_CRL_get_nextUpdate(crlInternal)) {
134             retVal = X509_cmp_current_time(
135                     X509_CRL_get_nextUpdate(crlInternal));
136             retStatus.isCRLValid = retVal > 0;
137         } else {
138             // If nextUpdate is not set assume it is actual.
139             retStatus.isCRLValid = true;
140         }
141         WrtLogI("CRL valid: %d", retStatus.isCRLValid);
142         X509_REVOKED rev;
143         rev.serialNumber = X509_get_serialNumber(argCert->getX509());
144         // sk_X509_REVOKED_find returns index if serial number is found on list
145         retVal = sk_X509_REVOKED_find(crlInternal->crl->revoked, &rev);
146         X509_CRL_free(crlInternal);
147         retStatus.isRevoked = retVal != -1;
148         WrtLogI("CRL revoked: %d", retStatus.isRevoked);
149
150         if (!retStatus.isRevoked && isOutOfDate(crl)) {
151             WrtLogD("Certificate is not Revoked, but CRL is outOfDate.");
152             continue;
153         }
154
155         return retStatus;
156     }
157     // If there is no CRL for any of URIs it means it's not possible to
158     // tell anything about revocation status but it's is not an error.
159     return retStatus;
160 }
161
162 CRL::RevocationStatus CRLImpl::checkCertificateChain(CertificateCollection certChain)
163 {
164     if (!certChain.sort())
165         VcoreThrowMsg(CRLException::InvalidParameter,
166                       "Certificate list doesn't create chain.");
167
168     CRL::RevocationStatus ret;
169     ret.isCRLValid = true;
170     ret.isRevoked = false;
171     const CertificateList &certList = certChain.getChain();
172     FOREACH(it, certList) {
173         if (!(*it)->isRootCert()) {
174             WrtLogI("Certificate common name: %s", (*it)->getCommonName().c_str());
175             CRL::RevocationStatus certResult = checkCertificate(*it);
176             ret.isCRLValid &= certResult.isCRLValid;
177             ret.isRevoked |= certResult.isRevoked;
178             if (ret.isCRLValid && !ret.isRevoked) {
179                 addToStore(*it);
180             }
181
182             if (ret.isRevoked) {
183                 return ret;
184             }
185         }
186     }
187
188     return ret;
189 }
190
191 VerificationStatus CRLImpl::checkEndEntity(CertificateCollection &chain)
192 {
193     if (!chain.sort() && !chain.empty()) {
194         WrtLogI("Could not find End Entity certificate. "
195                 "Collection does not form chain.");
196         return VERIFICATION_STATUS_ERROR;
197     }
198     CertificateList::const_iterator iter = chain.begin();
199     CRL::RevocationStatus stat = checkCertificate(*iter);
200     if (stat.isRevoked) {
201         return VERIFICATION_STATUS_REVOKED;
202     }
203     if (stat.isCRLValid) {
204         return VERIFICATION_STATUS_GOOD;
205     }
206     return VERIFICATION_STATUS_ERROR;
207 }
208
209 void CRLImpl::addToStore(const CertificatePtr &argCert)
210 {
211     X509_STORE_add_cert(m_store, argCert->getX509());
212 }
213
214 bool CRLImpl::isOutOfDate(const CRLDataPtr &crl) const {
215     X509_CRL *crlInternal = convertToInternal(crl);
216
217     bool result = false;
218     if (X509_CRL_get_nextUpdate(crlInternal)) {
219         if (0 > X509_cmp_current_time(X509_CRL_get_nextUpdate(crlInternal))) {
220             result = true;
221         } else {
222             result = false;
223         }
224     } else {
225         result = true;
226     }
227     X509_CRL_free(crlInternal);
228     return result;
229 }
230
231 bool CRLImpl::updateList(const CertificatePtr &argCert,
232     const CRL::UpdatePolicy updatePolicy)
233 {
234     WrtLogI("Update CRL for certificate");
235
236     // Retrieve distribution points
237     CRL::StringList crlUris = getCrlUris(argCert);
238     FOREACH(it, crlUris) {
239         // Try to get CRL from database
240         WrtLogI("Getting CRL for URI: %s", (*it).c_str());
241
242         bool downloaded = false;
243
244         CRLDataPtr crl;
245
246         // If updatePolicy == UPDATE_ON_DEMAND we dont care
247         // about data in cache. New crl must be downloaded.
248         if (updatePolicy == CRL::UPDATE_ON_EXPIRED) {
249             crl = getCRL(*it);
250         }
251
252         if (!!crl && isOutOfDate(crl)) {
253             WrtLogD("Crl out of date - downloading.");
254             crl = downloadCRL(*it);
255             downloaded = true;
256         }
257
258         if (!crl) {
259             WrtLogD("Crl not found in cache - downloading.");
260             crl = downloadCRL(*it);
261             downloaded = true;
262         }
263
264         if (!crl) {
265             WrtLogD("Failed to obtain CRL. URL: %s", (*it).c_str());
266             continue;
267         }
268
269         if (!!crl && isOutOfDate(crl)) {
270             WrtLogE("CRL out of date. Broken URL: %s", (*it).c_str());
271         }
272
273         // Make X509 internal structure
274         X509_CRL *crlInternal = convertToInternal(crl);
275
276         //Check if CRL is signed
277         if (!verifyCRL(crlInternal, argCert)) {
278             WrtLogE("Failed to verify CRL. URI: %s", (crl->uri).c_str());
279             X509_CRL_free(crlInternal);
280             return false;
281         }
282         X509_CRL_free(crlInternal);
283
284         if (downloaded) {
285             updateCRL(crl);
286         }
287         return true;
288     }
289
290     return false;
291 }
292
293 void CRLImpl::addToStore(const CertificateCollection &collection)
294 {
295     FOREACH(it, collection){
296         addToStore(*it);
297     }
298 }
299
300 bool CRLImpl::updateList(const CertificateCollection &collection,
301     CRL::UpdatePolicy updatePolicy)
302 {
303     bool failed = false;
304
305     FOREACH(it, collection){
306         failed |= !updateList(*it, updatePolicy);
307     }
308
309     return !failed;
310 }
311
312 bool CRLImpl::verifyCRL(X509_CRL *crl,
313                     const CertificatePtr &cert)
314 {
315     X509_OBJECT obj;
316     X509_STORE_CTX *ctx = createContext(cert);
317
318     /* get issuer certificate */
319     int retVal = X509_STORE_get_by_subject(ctx, X509_LU_X509,
320                                            X509_CRL_get_issuer(crl), &obj);
321     X509_STORE_CTX_free(ctx);
322     if (0 >= retVal) {
323         WrtLogE("Unknown CRL issuer certificate!");
324         return false;
325     }
326
327     /* extract public key and verify signature */
328     EVP_PKEY *pkey = X509_get_pubkey(obj.data.x509);
329     X509_OBJECT_free_contents(&obj);
330     if (!pkey) {
331         WrtLogE("Failed to get issuer's public key.");
332         return false;
333     }
334     retVal = X509_CRL_verify(crl, pkey);
335     EVP_PKEY_free(pkey);
336     if (0 > retVal) {
337         WrtLogE("Failed to verify CRL.");
338         return false;
339     } else if (0 == retVal) {
340         WrtLogE("CRL is invalid");
341         return false;
342     }
343     WrtLogI("CRL is valid.");
344     return true;
345 }
346
347 bool CRLImpl::isPEMFormat(const CRLDataPtr &crl) const
348 {
349     const char *pattern = "-----BEGIN X509 CRL-----";
350     std::string content(crl->buffer, crl->length);
351     if (content.find(pattern) != std::string::npos) {
352         WrtLogI("CRL is in PEM format.");
353         return true;
354     }
355     WrtLogI("CRL is in DER format.");
356     return false;
357 }
358
359 X509_CRL *CRLImpl::convertToInternal(const CRLDataPtr &crl) const
360 {
361     //At this point it's not clear does crl have DER or PEM format
362     X509_CRL *ret = NULL;
363     if (isPEMFormat(crl)) {
364         BIO *bmem = BIO_new_mem_buf(crl->buffer, crl->length);
365         if (!bmem)
366             VcoreThrowMsg(CRLException::InternalError,
367                           "Failed to allocate memory in BIO");
368
369         ret = PEM_read_bio_X509_CRL(bmem, NULL, NULL, NULL);
370         BIO_free_all(bmem);
371     } else {
372         //If it's not PEM it must be DER format
373         std::string content(crl->buffer, crl->length);
374         const unsigned char *buffer =
375             reinterpret_cast<unsigned char*>(crl->buffer);
376         ret = d2i_X509_CRL(NULL, &buffer, crl->length);
377     }
378
379     if (!ret)
380         VcoreThrowMsg(CRLException::InternalError,
381                       "Failed to convert to internal structure");
382     return ret;
383 }
384
385 X509_STORE_CTX *CRLImpl::createContext(const CertificatePtr &argCert)
386 {
387     X509_STORE_CTX *ctx;
388     ctx = X509_STORE_CTX_new();
389     if (!ctx)
390         VcoreThrowMsg(CRLException::StorageError, "Failed to create new ctx");
391
392     X509_STORE_CTX_init(ctx, m_store, argCert->getX509(), NULL);
393     return ctx;
394 }
395
396 CRLImpl::CRLDataPtr CRLImpl::downloadCRL(const std::string &uri)
397 {
398     using namespace SoupWrapper;
399
400     char *cport = 0, *chost = 0,*cpath = 0;
401     int use_ssl = 0;
402
403     if (!OCSP_parse_url(const_cast<char*>(uri.c_str()),
404                         &chost,
405                         &cport,
406                         &cpath,
407                         &use_ssl))
408     {
409         WrtLogW("Error in OCSP_parse_url");
410         return CRLDataPtr();
411     }
412
413     std::string host = chost;
414     if (cport) {
415         host += ":";
416         host += cport;
417     }
418
419     free(cport);
420     free(chost);
421     free(cpath);
422
423     SoupMessageSendSync message;
424     message.setHost(uri);
425     message.setHeader("Host", host);
426
427     if (SoupMessageSendSync::REQUEST_STATUS_OK != message.sendSync()) {
428         WrtLogW("Error in sending network request.");
429         return CRLDataPtr();
430     }
431
432     SoupMessageSendBase::MessageBuffer mBuffer = message.getResponse();
433     return CRLDataPtr(new CRLData(mBuffer,uri));
434 }
435
436 CRLImpl::CRLDataPtr CRLImpl::getCRL(const std::string &uri) const
437 {
438     CRLCachedData cachedCrl;
439     cachedCrl.distribution_point = uri;
440     if (!(m_crlCache->getCRLResponse(&cachedCrl))) {
441         WrtLogI("CRL not present in database. URI: %s", uri.c_str());
442         return CRLDataPtr();
443     }
444
445     std::string body = cachedCrl.crl_body;
446
447     WrtLogI("CRL found in database.");
448     //TODO: remove when ORM::blob available
449     //Encode buffer to base64 format to store in database
450
451     Base64Decoder decoder;
452     decoder.append(body);
453     if (!decoder.finalize())
454         VcoreThrowMsg(CRLException::StorageError,
455                       "Failed to decode base64 format.");
456     std::string crlBody = decoder.get();
457
458     std::unique_ptr<char[]> bodyBuffer(new char[crlBody.length()]);
459     crlBody.copy(bodyBuffer.get(), crlBody.length());
460     return CRLDataPtr(new CRLData(bodyBuffer.release(), crlBody.length(),
461                                   uri));
462 }
463
464 void CRLImpl::updateCRL(const CRLDataPtr &crl)
465 {
466     //TODO: remove when ORM::blob available
467     //Encode buffer to base64 format to store in database
468     Base64Encoder encoder;
469     if (!crl || !crl->buffer)
470         VcoreThrowMsg(CRLException::InternalError, "CRL buffer is empty");
471
472     encoder.append(std::string(crl->buffer, crl->length));
473     encoder.finalize();
474     std::string b64CRLBody = encoder.get();
475
476     time_t nextUpdateTime = 0;
477     X509_CRL *crlInternal = convertToInternal(crl);
478
479     if (X509_CRL_get_nextUpdate(crlInternal)) {
480         asn1TimeToTimeT(X509_CRL_get_nextUpdate(crlInternal),
481                         &nextUpdateTime);
482     }
483
484     X509_CRL_free(crlInternal);
485     //Update/insert crl body
486     CRLCachedData data;
487     data.distribution_point = crl->uri;
488     data.crl_body = b64CRLBody;
489     data.next_update_time = nextUpdateTime;
490
491     m_crlCache->setCRLResponse(&data);
492 }
493
494 } // namespace ValidationCore