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