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
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>
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>
43 #include "Certificate.h"
44 #include "SoupMessageSendSync.h"
45 #include "CertificateCacheDAO.h"
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
52 namespace ValidationCore {
54 CRL::StringList CRL::getCrlUris(const CertificatePtr &argCert)
56 StringList result = argCert->getCrlUris();
58 if (!result.empty()) {
61 LogInfo("No distribution points found. Getting from CA cert.");
62 X509_STORE_CTX *ctx = createContext(argCert);
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->
70 X509_STORE_CTX_free(ctx);
72 LogError("No dedicated CA certificate available");
75 CertificatePtr caCert(new Certificate(obj.data.x509));
76 X509_OBJECT_free_contents(&obj);
77 return caCert->getCrlUris();
82 LogInfo("CRL storage initialization.");
83 m_store = X509_STORE_new();
85 LogError("Failed to create new store.");
86 ThrowMsg(CRLException::StorageError,
87 "Not possible to create new store.");
89 m_lookup = X509_STORE_add_lookup(m_store, X509_LOOKUP_hash_dir());
92 LogError("Failed to add hash dir lookup");
93 ThrowMsg(CRLException::StorageError,
94 "Not possible to add hash dir lookup.");
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);
106 LogError("Failed to add lookup dir for PEM files.");
108 ThrowMsg(CRLException::StorageError,
109 "Failed to add lookup dir for PEM files.");
111 LogInfo("CRL storage initialization complete.");
121 LogInfo("Free CRL storage");
122 // STORE is responsible for LOOKUP release
123 // X509_LOOKUP_free(m_lookup);
124 X509_STORE_free(m_store);
127 CRL::RevocationStatus CRL::checkCertificate(const CertificatePtr &argCert)
129 RevocationStatus retStatus = {false, false};
131 StringList crlUris = getCrlUris(argCert);
132 FOREACH(it, crlUris) {
133 CRLDataPtr crl = getCRL(*it);
135 LogDebug("CRL not found for URI: " << *it);
138 X509_CRL *crlInternal = convertToInternal(crl);
141 if (X509_CRL_get_nextUpdate(crlInternal)) {
142 retVal = X509_cmp_current_time(
143 X509_CRL_get_nextUpdate(crlInternal));
144 retStatus.isCRLValid = retVal > 0;
146 // If nextUpdate is not set assume it is actual.
147 retStatus.isCRLValid = true;
149 LogInfo("CRL valid: " << retStatus.isCRLValid);
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);
158 if (!retStatus.isRevoked && isOutOfDate(crl)) {
159 LogDebug("Certificate is not Revoked, but CRL is outOfDate.");
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.
170 CRL::RevocationStatus CRL::checkCertificateChain(CertificateCollection
173 if (!certChain.sort()) {
174 LogError("Certificate list doesn't create chain.");
175 ThrowMsg(CRLException::InvalidParameter,
176 "Certificate list doesn't create chain.");
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) {
200 VerificationStatus CRL::checkEndEntity(CertificateCollection &chain)
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;
207 CertificateList::const_iterator iter = chain.begin();
208 RevocationStatus stat = checkCertificate(*iter);
209 if (stat.isRevoked) {
210 return VERIFICATION_STATUS_REVOKED;
212 if (stat.isCRLValid) {
213 return VERIFICATION_STATUS_GOOD;
215 return VERIFICATION_STATUS_ERROR;
218 void CRL::addToStorage(const CertificatePtr &argCert)
220 X509_STORE_add_cert(m_store, argCert->getX509());
223 bool CRL::isOutOfDate(const CRLDataPtr &crl) const {
224 X509_CRL *crlInternal = convertToInternal(crl);
227 if (X509_CRL_get_nextUpdate(crlInternal)) {
228 if (0 > X509_cmp_current_time(X509_CRL_get_nextUpdate(crlInternal))) {
236 X509_CRL_free(crlInternal);
240 bool CRL::updateList(const CertificatePtr &argCert,
241 const UpdatePolicy updatePolicy)
243 LogInfo("Update CRL for certificate");
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);
251 bool downloaded = false;
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) {
261 if (!!crl && isOutOfDate(crl)) {
262 LogDebug("Crl out of date - downloading.");
263 crl = downloadCRL(*it);
268 LogDebug("Crl not found in cache - downloading.");
269 crl = downloadCRL(*it);
274 LogDebug("Failed to obtain CRL. URL: " << *it);
278 if (!!crl && isOutOfDate(crl)) {
279 LogError("CRL out of date. Broken URL: " << *it);
282 // Make X509 internal structure
283 X509_CRL *crlInternal = convertToInternal(crl);
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);
291 X509_CRL_free(crlInternal);
302 void CRL::addToStore(const CertificateCollection &collection)
304 FOREACH(it, collection){
309 bool CRL::updateList(const CertificateCollection &collection,
310 UpdatePolicy updatePolicy)
314 FOREACH(it, collection){
315 failed |= !updateList(*it, updatePolicy);
321 bool CRL::verifyCRL(X509_CRL *crl,
322 const CertificatePtr &cert)
325 X509_STORE_CTX *ctx = createContext(cert);
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);
332 LogError("Unknown CRL issuer certificate!");
336 /* extract public key and verify signature */
337 EVP_PKEY *pkey = X509_get_pubkey(obj.data.x509);
338 X509_OBJECT_free_contents(&obj);
340 LogError("Failed to get issuer's public key.");
343 retVal = X509_CRL_verify(crl, pkey);
346 LogError("Failed to verify CRL.");
348 } else if (0 == retVal) {
349 LogError("CRL is invalid");
352 LogInfo("CRL is valid.");
356 bool CRL::isPEMFormat(const CRLDataPtr &crl) const
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.");
364 LogInfo("CRL is in DER format.");
368 X509_CRL *CRL::convertToInternal(const CRLDataPtr &crl) const
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);
375 LogError("Failed to allocate memory in BIO");
376 ThrowMsg(CRLException::InternalError,
377 "Failed to allocate memory in BIO");
379 ret = PEM_read_bio_X509_CRL(bmem, NULL, NULL, NULL);
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);
389 LogError("Failed to convert to internal structure");
390 ThrowMsg(CRLException::InternalError,
391 "Failed to convert to internal structure");
396 X509_STORE_CTX *CRL::createContext(const CertificatePtr &argCert)
399 ctx = X509_STORE_CTX_new();
401 ThrowMsg(CRLException::StorageError, "Failed to create new context.");
403 X509_STORE_CTX_init(ctx, m_store, argCert->getX509(), NULL);
407 CRL::CRLDataPtr CRL::downloadCRL(const std::string &uri)
409 using namespace SoupWrapper;
411 char *cport = 0, *chost = 0,*cpath = 0;
414 if (!OCSP_parse_url(const_cast<char*>(uri.c_str()),
420 LogWarning("Error in OCSP_parse_url");
424 std::string host = chost;
434 SoupMessageSendSync message;
435 message.setHost(uri);
436 message.setHeader("Host", host);
438 if (SoupMessageSendSync::REQUEST_STATUS_OK != message.sendSync()) {
439 LogWarning("Error in sending network request.");
443 SoupMessageSendBase::MessageBuffer mBuffer = message.getResponse();
444 return CRLDataPtr(new CRLData(mBuffer,uri));
447 CRL::CRLDataPtr CRL::getCRL(const std::string &uri) const
449 CRLCachedData cachedCrl;
450 cachedCrl.distribution_point = uri;
451 if (!CertificateCacheDAO::getCRLResponse(&cachedCrl)) {
452 LogInfo("CRL not present in database. URI: " << uri);
456 std::string body = cachedCrl.crl_body;
458 LogInfo("CRL found in database.");
459 //TODO: remove when ORM::blob available
460 //Encode buffer to base64 format to store in database
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.");
468 std::string crlBody = decoder.get();
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(),
476 void CRL::updateCRL(const CRLDataPtr &crl)
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");
484 encoder.append(std::string(crl->buffer, crl->length));
486 std::string b64CRLBody = encoder.get();
488 time_t nextUpdateTime = 0;
489 X509_CRL *crlInternal = convertToInternal(crl);
491 if (X509_CRL_get_nextUpdate(crlInternal)) {
492 asn1TimeToTimeT(X509_CRL_get_nextUpdate(crlInternal),
496 X509_CRL_free(crlInternal);
497 //Update/insert crl body
498 CertificateCacheDAO::setCRLResponse(crl->uri,b64CRLBody,nextUpdateTime);