2 * Copyright (c) 2011 Samsung Electronics Co., Ltd All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 * @author Piotr Marcinkiewicz(p.marcinkiew@samsung.com)
20 * @brief Routines for certificate validation over CRL
23 #include <vcore/CRL.h>
24 #include <vcore/CRLImpl.h>
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>
35 #include <dpl/log/log.h>
36 #include <dpl/assert.h>
37 #include <dpl/db/orm.h>
38 #include <dpl/foreach.h>
40 #include <vcore/Base64.h>
41 #include <vcore/Certificate.h>
42 #include <vcore/SoupMessageSendSync.h>
43 #include <vcore/CRLCacheInterface.h>
46 const char *CRL_LOOKUP_DIR = "/usr/share/ca-certificates/wac";
47 } //anonymous namespace
49 namespace ValidationCore {
51 CRL::StringList CRLImpl::getCrlUris(const CertificatePtr &argCert)
53 CRL::StringList result = argCert->getCrlUris();
58 LogInfo("No distribution points found. Getting from CA cert.");
59 X509_STORE_CTX *ctx = createContext(argCert);
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->
67 X509_STORE_CTX_free(ctx);
69 LogError("No dedicated CA certificate available");
72 CertificatePtr caCert(new Certificate(obj.data.x509));
73 X509_OBJECT_free_contents(&obj);
74 return caCert->getCrlUris();
77 CRLImpl::CRLImpl(CRLCacheInterface *ptr)
80 Assert(m_crlCache != NULL);
82 LogInfo("CRL storage initialization.");
83 m_store = X509_STORE_new();
85 VcoreThrowMsg(CRLException::StorageError,
86 "impossible to create new store");
88 m_lookup = X509_STORE_add_lookup(m_store, X509_LOOKUP_hash_dir());
91 VcoreThrowMsg(CRLException::StorageError,
92 "impossible to add hash dir lookup");
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;
99 VcoreThrowMsg(CRLException::StorageError,
100 "Failed to add lookup dir for PEM files");
102 LogInfo("CRL storage initialization complete.");
111 void CRLImpl::cleanup()
113 LogInfo("Free CRL storage");
114 // STORE is responsible for LOOKUP release
115 // X509_LOOKUP_free(m_lookup);
116 X509_STORE_free(m_store);
119 CRL::RevocationStatus CRLImpl::checkCertificate(const CertificatePtr &argCert)
121 CRL::RevocationStatus retStatus = {false, false};
123 CRL::StringList crlUris = getCrlUris(argCert);
124 FOREACH(it, crlUris) {
125 CRLDataPtr crl = getCRL(*it);
127 LogDebug("CRL not found for URI: " << *it);
130 X509_CRL *crlInternal = convertToInternal(crl);
133 if (X509_CRL_get_nextUpdate(crlInternal)) {
134 retVal = X509_cmp_current_time(
135 X509_CRL_get_nextUpdate(crlInternal));
136 retStatus.isCRLValid = retVal > 0;
138 // If nextUpdate is not set assume it is actual.
139 retStatus.isCRLValid = true;
141 LogInfo("CRL valid: " << retStatus.isCRLValid);
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 LogInfo("CRL revoked: " << retStatus.isRevoked);
150 if (!retStatus.isRevoked && isOutOfDate(crl)) {
151 LogDebug("Certificate is not Revoked, but CRL is outOfDate.");
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.
162 CRL::RevocationStatus CRLImpl::checkCertificateChain(CertificateCollection certChain)
164 if (!certChain.sort())
165 VcoreThrowMsg(CRLException::InvalidParameter,
166 "Certificate list doesn't create chain.");
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 LogInfo("Certificate common name: " << (*it)->getCommonName());
175 CRL::RevocationStatus certResult = checkCertificate(*it);
176 ret.isCRLValid &= certResult.isCRLValid;
177 ret.isRevoked |= certResult.isRevoked;
178 if (ret.isCRLValid && !ret.isRevoked) {
191 VerificationStatus CRLImpl::checkEndEntity(CertificateCollection &chain)
193 if (!chain.sort() && !chain.empty()) {
194 LogInfo("Could not find End Entity certificate. "
195 "Collection does not form chain.");
196 return VERIFICATION_STATUS_ERROR;
198 CertificateList::const_iterator iter = chain.begin();
199 CRL::RevocationStatus stat = checkCertificate(*iter);
200 if (stat.isRevoked) {
201 return VERIFICATION_STATUS_REVOKED;
203 if (stat.isCRLValid) {
204 return VERIFICATION_STATUS_GOOD;
206 return VERIFICATION_STATUS_ERROR;
209 void CRLImpl::addToStore(const CertificatePtr &argCert)
211 X509_STORE_add_cert(m_store, argCert->getX509());
214 bool CRLImpl::isOutOfDate(const CRLDataPtr &crl) const {
215 X509_CRL *crlInternal = convertToInternal(crl);
218 if (X509_CRL_get_nextUpdate(crlInternal)) {
219 if (0 > X509_cmp_current_time(X509_CRL_get_nextUpdate(crlInternal))) {
227 X509_CRL_free(crlInternal);
231 bool CRLImpl::updateList(const CertificatePtr &argCert,
232 const CRL::UpdatePolicy updatePolicy)
234 LogInfo("Update CRL for certificate");
236 // Retrieve distribution points
237 CRL::StringList crlUris = getCrlUris(argCert);
238 FOREACH(it, crlUris) {
239 // Try to get CRL from database
240 LogInfo("Getting CRL for URI: " << *it);
242 bool downloaded = false;
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) {
252 if (!!crl && isOutOfDate(crl)) {
253 LogDebug("Crl out of date - downloading.");
254 crl = downloadCRL(*it);
259 LogDebug("Crl not found in cache - downloading.");
260 crl = downloadCRL(*it);
265 LogDebug("Failed to obtain CRL. URL: " << *it);
269 if (!!crl && isOutOfDate(crl)) {
270 LogError("CRL out of date. Broken URL: " << *it);
273 // Make X509 internal structure
274 X509_CRL *crlInternal = convertToInternal(crl);
276 //Check if CRL is signed
277 if (!verifyCRL(crlInternal, argCert)) {
278 LogError("Failed to verify CRL. URI: " << (crl->uri).c_str());
279 X509_CRL_free(crlInternal);
282 X509_CRL_free(crlInternal);
293 void CRLImpl::addToStore(const CertificateCollection &collection)
295 FOREACH(it, collection){
300 bool CRLImpl::updateList(const CertificateCollection &collection,
301 CRL::UpdatePolicy updatePolicy)
305 FOREACH(it, collection){
306 failed |= !updateList(*it, updatePolicy);
312 bool CRLImpl::verifyCRL(X509_CRL *crl,
313 const CertificatePtr &cert)
316 X509_STORE_CTX *ctx = createContext(cert);
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);
323 LogError("Unknown CRL issuer certificate!");
327 /* extract public key and verify signature */
328 EVP_PKEY *pkey = X509_get_pubkey(obj.data.x509);
329 X509_OBJECT_free_contents(&obj);
331 LogError("Failed to get issuer's public key.");
334 retVal = X509_CRL_verify(crl, pkey);
337 LogError("Failed to verify CRL.");
339 } else if (0 == retVal) {
340 LogError("CRL is invalid");
343 LogInfo("CRL is valid.");
347 bool CRLImpl::isPEMFormat(const CRLDataPtr &crl) const
349 const char *pattern = "-----BEGIN X509 CRL-----";
350 std::string content(crl->buffer, crl->length);
351 if (content.find(pattern) != std::string::npos) {
352 LogInfo("CRL is in PEM format.");
355 LogInfo("CRL is in DER format.");
359 X509_CRL *CRLImpl::convertToInternal(const CRLDataPtr &crl) const
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);
366 VcoreThrowMsg(CRLException::InternalError,
367 "Failed to allocate memory in BIO");
369 ret = PEM_read_bio_X509_CRL(bmem, NULL, NULL, NULL);
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);
380 VcoreThrowMsg(CRLException::InternalError,
381 "Failed to convert to internal structure");
385 X509_STORE_CTX *CRLImpl::createContext(const CertificatePtr &argCert)
388 ctx = X509_STORE_CTX_new();
390 VcoreThrowMsg(CRLException::StorageError, "Failed to create new ctx");
392 X509_STORE_CTX_init(ctx, m_store, argCert->getX509(), NULL);
396 CRLImpl::CRLDataPtr CRLImpl::downloadCRL(const std::string &uri)
398 using namespace SoupWrapper;
400 char *cport = 0, *chost = 0,*cpath = 0;
403 if (!OCSP_parse_url(const_cast<char*>(uri.c_str()),
409 LogWarning("Error in OCSP_parse_url");
413 std::string host = chost;
423 SoupMessageSendSync message;
424 message.setHost(uri);
425 message.setHeader("Host", host);
427 if (SoupMessageSendSync::REQUEST_STATUS_OK != message.sendSync()) {
428 LogWarning("Error in sending network request.");
432 SoupMessageSendBase::MessageBuffer mBuffer = message.getResponse();
433 return CRLDataPtr(new CRLData(mBuffer,uri));
436 CRLImpl::CRLDataPtr CRLImpl::getCRL(const std::string &uri) const
438 CRLCachedData cachedCrl;
439 cachedCrl.distribution_point = uri;
440 if (!(m_crlCache->getCRLResponse(&cachedCrl))) {
441 LogInfo("CRL not present in database. URI: " << uri);
445 std::string body = cachedCrl.crl_body;
447 LogInfo("CRL found in database.");
448 //TODO: remove when ORM::blob available
449 //Encode buffer to base64 format to store in database
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();
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(),
464 void CRLImpl::updateCRL(const CRLDataPtr &crl)
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");
472 encoder.append(std::string(crl->buffer, crl->length));
474 std::string b64CRLBody = encoder.get();
476 time_t nextUpdateTime = 0;
477 X509_CRL *crlInternal = convertToInternal(crl);
479 if (X509_CRL_get_nextUpdate(crlInternal)) {
480 asn1TimeToTimeT(X509_CRL_get_nextUpdate(crlInternal),
484 X509_CRL_free(crlInternal);
485 //Update/insert crl body
487 data.distribution_point = crl->uri;
488 data.crl_body = b64CRLBody;
489 data.next_update_time = nextUpdateTime;
491 m_crlCache->setCRLResponse(&data);
494 } // namespace ValidationCore