+ static string XMLTOOL_DLLLOCAL X509_NAME_to_string(X509_NAME* n)
+ {
+ string s;
+ BIO* b = BIO_new(BIO_s_mem());
+ X509_NAME_print_ex(b,n,0,XN_FLAG_RFC2253);
+ BIO_flush(b);
+ BUF_MEM* bptr=nullptr;
+ BIO_get_mem_ptr(b, &bptr);
+ if (bptr && bptr->length > 0) {
+ s.append(bptr->data, bptr->length);
+ }
+ BIO_free(b);
+ return s;
+ }
+
+ static void XMLTOOL_DLLLOCAL getRemoteCRLs(vector<XSECCryptoX509CRL*>& crls, const char* cdpuri, Category& log) {
+ // This is a temporary CRL cache implementation to avoid breaking binary compatibility
+ // for the library. Caching can't rely on any member objects within the TrustEngine,
+ // including locks, so we're using the global library lock for the time being.
+ // All other state is kept in the file system.
+
+ // The filenames for the CRL cache are based on a hash of the CRL location.
+ string cdpfile = SecurityHelper::doHash("SHA1", cdpuri, strlen(cdpuri)) + ".crl";
+ XMLToolingConfig::getConfig().getPathResolver()->resolve(cdpfile, PathResolver::XMLTOOLING_RUN_FILE);
+ string cdpstaging = cdpfile + ".bak";
+ string counterfile = cdpfile + ".cnt";
+
+ // Need to move this to a configurable parameter once we can break binary compatibility.
+ // Ideally this would be based on a percentage of the original CRL window, but OpenSSL
+ // doesn't provide much in the way of ASN1_TIME parsing functions. It does support adding
+ // a fixed time value and comparing against known times.
+ #define MIN_SECS_REMAINING 86400;
+ long counter = 0;
+ try {
+ // While holding the lock, check for a cached copy of the CRL, and check its validity.
+ Locker glock(&XMLToolingConfig::getConfig());
+#ifdef WIN32
+ struct _stat stat_buf;
+ if (_stat(cdpfile.c_str(), &stat_buf) == 0) {
+#else
+ struct stat stat_buf;
+ if (stat(cdpfile.c_str(), &stat_buf) == 0) {
+#endif
+ SecurityHelper::loadCRLsFromFile(crls, cdpfile.c_str());
+ if (crls.empty() || crls.front()->getProviderName() != DSIGConstants::s_unicodeStrPROVOpenSSL ||
+ X509_cmp_time(X509_CRL_get_nextUpdate(static_cast<OpenSSLCryptoX509CRL*>(crls.front())->getOpenSSLX509CRL()), nullptr) < 0) {
+ for_each(crls.begin(), crls.end(), xmltooling::cleanup<XSECCryptoX509CRL>());
+ crls.clear();
+ remove(cdpfile.c_str()); // may as well delete the local copy
+ remove(counterfile.c_str());
+ log.info("cached CRL(s) from (%s) have expired", cdpuri);
+ }
+ else {
+ // Look for a file containing the allowable time remaining on the CRL.
+ // We store this counter in the file system because of the binary compatibility issue.
+ try {
+ ifstream countersrc(counterfile.c_str());
+ if (countersrc)
+ countersrc >> counter;
+ }
+ catch (exception&) {
+ counter = 0;
+ }
+ if (counter == 0)
+ counter = MIN_SECS_REMAINING;
+
+ // See if the time left is under the counter threshold.
+ time_t exp = time(nullptr) + counter;
+ if (X509_cmp_time(X509_CRL_get_nextUpdate(static_cast<OpenSSLCryptoX509CRL*>(crls.front())->getOpenSSLX509CRL()), &exp) < 0) {
+ for_each(crls.begin(), crls.end(), xmltooling::cleanup<XSECCryptoX509CRL>());
+ crls.clear();
+ log.info("cached CRL(s) from (%s) will expire within %ld seconds, attempting to update them", cdpuri, counter);
+ }
+ }
+ }
+ }
+ catch (exception& ex) {
+ log.error("exception loading cached copy of CRL at (%s): %s", cdpuri, ex.what());
+ }
+
+ if (crls.empty()) {
+ try {
+ // If we get here, the cached copy didn't exist yet, or it's time to refresh.
+ SOAPTransport::Address addr("AbstractPKIXTrustEngine", cdpuri, cdpuri);
+ string scheme(addr.m_endpoint, strchr(addr.m_endpoint,':') - addr.m_endpoint);
+ auto_ptr<SOAPTransport> soap(XMLToolingConfig::getConfig().SOAPTransportManager.newPlugin(scheme.c_str(), addr));
+ soap->send();
+ istream& msg = soap->receive();
+ Locker glock(&XMLToolingConfig::getConfig());
+ ofstream out(cdpstaging.c_str(), fstream::trunc|fstream::binary);
+ out << msg.rdbuf();
+ out.close();
+ SecurityHelper::loadCRLsFromFile(crls, cdpstaging.c_str());
+ if (crls.empty() || crls.front()->getProviderName() != DSIGConstants::s_unicodeStrPROVOpenSSL ||
+ X509_cmp_time(X509_CRL_get_nextUpdate(static_cast<OpenSSLCryptoX509CRL*>(crls.front())->getOpenSSLX509CRL()), nullptr) < 0) {
+ // The "new" CRLs weren't usable, so get rid of them.
+ for_each(crls.begin(), crls.end(), xmltooling::cleanup<XSECCryptoX509CRL>());
+ crls.clear();
+ remove(cdpstaging.c_str());
+ log.error("updated CRL(s) from (%s) have already expired", cdpuri);
+
+ // If counter isn't 0, then we were attempting an update of still-valid CRLs, so reload the old ones
+ // and cut the counter in half for next time.
+ if (counter > 0) {
+ SecurityHelper::loadCRLsFromFile(crls, cdpfile.c_str());
+ ofstream countersink(counterfile.c_str(), fstream::trunc);
+ counter /= 2;
+ countersink << counter;
+ log.info("failed CRL update attempt, reducing threshold to %ld seconds", counter);
+ }
+ }
+ else {
+ // If counter isn't zero, we reloaded; see if the new CRLs are "more" valid than before.
+ if (counter > 0) {
+ time_t exp = time(nullptr) + counter;
+ if (X509_cmp_time(X509_CRL_get_nextUpdate(static_cast<OpenSSLCryptoX509CRL*>(crls.front())->getOpenSSLX509CRL()), &exp) < 0) {
+ // Still invalid past the acceptable interval, so they're the same as what we had.
+ // Remove the extra copy, and cut the counter in half for next time.
+ remove(cdpstaging.c_str());
+ ofstream countersink(counterfile.c_str(), fstream::trunc);
+ counter /= 2;
+ countersink << counter;
+ log.info("remote CRL(s) unchanged, reducing threshold to %ld seconds", counter);
+ }
+ else {
+ counter = 0;
+ log.info("remote CRL(s) updated");
+ }
+ }
+
+ if (counter == 0) {
+ // "Commit" the new CRLs.
+ remove(cdpfile.c_str());
+ remove(counterfile.c_str());
+ if (rename(cdpstaging.c_str(), cdpfile.c_str()) != 0)
+ log.error("unable to rename CRL staging file");
+ }
+ }
+ }
+ catch (exception& ex) {
+ log.error("exception downloading/caching CRL at (%s): %s", cdpuri, ex.what());
+ }
+ }
+ }
+