/** * Check if X509Cert is signed by trusted issuer * @return 0 or openssl error_code. Get human readable cause with X509_verify_cert_error_string(code) * @throw IOException if error */ int digidoc::X509Cert::verify() const throw(IOException) { X509_STORE_CTX *csc = X509_STORE_CTX_new(); X509_STORE_CTX_scope csct(&csc); if (!csc) THROW_IOEXCEPTION("Failed to create X509_STORE_CTX %s",ERR_reason_error_string(ERR_get_error())); X509_STORE *store = digidoc::X509CertStore::getInstance()->getCertStore(); X509* x = getX509(); X509_scope xt(&x); if(!X509_STORE_CTX_init(csc, store, x, NULL)) THROW_IOEXCEPTION("Failed to init X509_STORE_CTX %s",ERR_reason_error_string(ERR_get_error())); int ok = X509_verify_cert(csc); if(!ok) { int err = X509_STORE_CTX_get_error(csc); X509Cert cause(X509_STORE_CTX_get_current_cert (csc)); std::ostringstream s; s << "Unable to verify " << cause.getSubject(); s << ". Cause: " << X509_verify_cert_error_string(err); switch(err) { case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: { IOException e(__FILE__, __LINE__, s.str()); e.setCode( Exception::CertificateIssuerMissing ); throw e; break; } default: THROW_IOEXCEPTION(s.str().c_str()); break; } } return ok; }
bool SSL_CTX_use_certificate_file(::SSL_CTX* ctx, const char* file, int /*type*/) { auto x509 = getX509(file); if (!x509) { return false; } return SSL_CTX_use_certificate(ctx, x509) == SSL_SUCCESS; }
void SSLContextManager::insert(shared_ptr<SSLContext> sslCtx, std::unique_ptr<SSLSessionCacheManager> smanager, std::unique_ptr<TLSTicketKeyManager> tmanager, bool defaultFallback) { X509* x509 = getX509(sslCtx->getSSLCtx()); auto guard = folly::makeGuard([x509] { X509_free(x509); }); auto cn = SSLUtil::getCommonName(x509); if (!cn) { throw std::runtime_error("Cannot get CN"); } /** * Some notes from RFC 2818. Only for future quick references in case of bugs * * RFC 2818 section 3.1: * "...... * If a subjectAltName extension of type dNSName is present, that MUST * be used as the identity. Otherwise, the (most specific) Common Name * field in the Subject field of the certificate MUST be used. Although * the use of the Common Name is existing practice, it is deprecated and * Certification Authorities are encouraged to use the dNSName instead. * ...... * In some cases, the URI is specified as an IP address rather than a * hostname. In this case, the iPAddress subjectAltName must be present * in the certificate and must exactly match the IP in the URI. * ......" */ // Not sure if we ever get this kind of X509... // If we do, assume '*' is always in the CN and ignore all subject alternative // names. if (cn->length() == 1 && (*cn)[0] == '*') { if (!defaultFallback) { throw std::runtime_error("STAR X509 is not the default"); } ctxs_.emplace_back(sslCtx); sessionCacheManagers_.emplace_back(std::move(smanager)); ticketManagers_.emplace_back(std::move(tmanager)); return; } // Insert by CN insertSSLCtxByDomainName(cn->c_str(), cn->length(), sslCtx); // Insert by subject alternative name(s) auto altNames = SSLUtil::getSubjectAltName(x509); if (altNames) { for (auto& name : *altNames) { insertSSLCtxByDomainName(name.c_str(), name.length(), sslCtx); } } ctxs_.emplace_back(sslCtx); sessionCacheManagers_.emplace_back(std::move(smanager)); ticketManagers_.emplace_back(std::move(tmanager)); }
bool bdoc::X509Cert::verify(X509_STORE* aStore, struct tm* tm) const { if (aStore == NULL) { THROW_STACK_EXCEPTION("Invalid argument to verify"); } X509_STORE* store = aStore; X509_STORE** ppStore = NULL; X509_STORE_scope xst(ppStore); X509_STORE_CTX *csc = X509_STORE_CTX_new(); X509_STORE_CTX_scope csct(&csc); if (csc == NULL) { THROW_STACK_EXCEPTION("Failed to create X509_STORE_CTX %s",ERR_reason_error_string(ERR_get_error())); } X509* x = getX509(); X509_scope xt(&x); if (!X509_STORE_CTX_init(csc, store, x, NULL)) { THROW_STACK_EXCEPTION("Failed to init X509_STORE_CTX %s",ERR_reason_error_string(ERR_get_error())); } if (tm != NULL) { time_t t = timegm(tm); if (t == -1) { THROW_STACK_EXCEPTION("Given time cannot be represented as calendar time"); } X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(csc); if (param == NULL) { THROW_STACK_EXCEPTION("Failed to retrieve X509_STORE_CTX verification parameters %s", ERR_reason_error_string(ERR_get_error())); } X509_VERIFY_PARAM_set_time(param, t); } int ok = X509_verify_cert(csc); if (ok != 1) { int err = X509_STORE_CTX_get_error(csc); X509Cert cause(X509_STORE_CTX_get_current_cert (csc)); std::ostringstream s; s << "Unable to verify " << cause.getSubject(); s << ". Cause: " << X509_verify_cert_error_string(err); switch (err) { case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: { THROW_STACK_EXCEPTION("Certificate issuer missing: %s", s.str().c_str()); } default: THROW_STACK_EXCEPTION(s.str().c_str()); break; } } return (ok == 1); }
void SSLContextManager::addSSLContextConfig( const SSLContextConfig& ctxConfig, const SSLCacheOptions& cacheOptions, const TLSTicketKeySeeds* ticketSeeds, const folly::SocketAddress& vipAddress, const std::shared_ptr<SSLCacheProvider>& externalCache) { unsigned numCerts = 0; std::string commonName; std::string lastCertPath; std::unique_ptr<std::list<std::string>> subjectAltName; auto sslCtx = std::make_shared<SSLContext>(ctxConfig.sslVersion); for (const auto& cert : ctxConfig.certificates) { try { sslCtx->loadCertificate(cert.certPath.c_str()); } catch (const std::exception& ex) { // The exception isn't very useful without the certificate path name, // so throw a new exception that includes the path to the certificate. string msg = folly::to<string>("error loading SSL certificate ", cert.certPath, ": ", folly::exceptionStr(ex)); LOG(ERROR) << msg; throw std::runtime_error(msg); } // Verify that the Common Name and (if present) Subject Alternative Names // are the same for all the certs specified for the SSL context. numCerts++; X509* x509 = getX509(sslCtx->getSSLCtx()); auto guard = folly::makeGuard([x509] { X509_free(x509); }); auto cn = SSLUtil::getCommonName(x509); if (!cn) { throw std::runtime_error(folly::to<string>("Cannot get CN for X509 ", cert.certPath)); } auto altName = SSLUtil::getSubjectAltName(x509); VLOG(2) << "cert " << cert.certPath << " CN: " << *cn; if (altName) { altName->sort(); VLOG(2) << "cert " << cert.certPath << " SAN: " << flattenList(*altName); } else { VLOG(2) << "cert " << cert.certPath << " SAN: " << "{none}"; } if (numCerts == 1) { commonName = *cn; subjectAltName = std::move(altName); } else { if (commonName != *cn) { throw std::runtime_error(folly::to<string>("X509 ", cert.certPath, " does not have same CN as ", lastCertPath)); } if (altName == nullptr) { if (subjectAltName != nullptr) { throw std::runtime_error(folly::to<string>("X509 ", cert.certPath, " does not have same SAN as ", lastCertPath)); } } else { if ((subjectAltName == nullptr) || (*altName != *subjectAltName)) { throw std::runtime_error(folly::to<string>("X509 ", cert.certPath, " does not have same SAN as ", lastCertPath)); } } } lastCertPath = cert.certPath; // TODO t4438250 - Add ECDSA support to the crypto_ssl offload server // so we can avoid storing the ECDSA private key in the // address space of the Internet-facing process. For // now, if cert name includes "-EC" to denote elliptic // curve, we load its private key even if the server as // a whole has been configured for async crypto. if (ctxConfig.isLocalPrivateKey || (cert.certPath.find("-EC") != std::string::npos)) { // The private key lives in the same process // This needs to be called before loadPrivateKey(). if (!cert.passwordPath.empty()) { auto sslPassword = std::make_shared<PasswordInFile>(cert.passwordPath); sslCtx->passwordCollector(sslPassword); } try { sslCtx->loadPrivateKey(cert.keyPath.c_str()); } catch (const std::exception& ex) { // Throw an error that includes the key path, so the user can tell // which key had a problem. string msg = folly::to<string>("error loading private SSL key ", cert.keyPath, ": ", folly::exceptionStr(ex)); LOG(ERROR) << msg; throw std::runtime_error(msg); } } } if (!ctxConfig.isLocalPrivateKey) { enableAsyncCrypto(sslCtx); } // Let the server pick the highest performing cipher from among the client's // choices. // // Let's use a unique private key for all DH key exchanges. // // Because some old implementations choke on empty fragments, most SSL // applications disable them (it's part of SSL_OP_ALL). This // will improve performance and decrease write buffer fragmentation. sslCtx->setOptions(SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_SINGLE_DH_USE | SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS); // Configure SSL ciphers list if (!ctxConfig.tls11Ciphers.empty()) { // FIXME: create a dummy SSL_CTX for cipher testing purpose? It can // remove the ordering dependency // Test to see if the specified TLS1.1 ciphers are valid. Note that // these will be overwritten by the ciphers() call below. sslCtx->setCiphersOrThrow(ctxConfig.tls11Ciphers); } // Important that we do this *after* checking the TLS1.1 ciphers above, // since we test their validity by actually setting them. sslCtx->ciphers(ctxConfig.sslCiphers); // Use a fix DH param DH* dh = get_dh2048(); SSL_CTX_set_tmp_dh(sslCtx->getSSLCtx(), dh); DH_free(dh); const string& curve = ctxConfig.eccCurveName; if (!curve.empty()) { set_key_from_curve(sslCtx->getSSLCtx(), curve); } if (!ctxConfig.clientCAFile.empty()) { try { sslCtx->setVerificationOption(SSLContext::VERIFY_REQ_CLIENT_CERT); sslCtx->loadTrustedCertificates(ctxConfig.clientCAFile.c_str()); sslCtx->loadClientCAList(ctxConfig.clientCAFile.c_str()); } catch (const std::exception& ex) { string msg = folly::to<string>("error loading client CA", ctxConfig.clientCAFile, ": ", folly::exceptionStr(ex)); LOG(ERROR) << msg; throw std::runtime_error(msg); } } // - start - SSL session cache config // the internal cache never does what we want (per-thread-per-vip). // Disable it. SSLSessionCacheManager will set it appropriately. SSL_CTX_set_session_cache_mode(sslCtx->getSSLCtx(), SSL_SESS_CACHE_OFF); SSL_CTX_set_timeout(sslCtx->getSSLCtx(), cacheOptions.sslCacheTimeout.count()); std::unique_ptr<SSLSessionCacheManager> sessionCacheManager; if (ctxConfig.sessionCacheEnabled && cacheOptions.maxSSLCacheSize > 0 && cacheOptions.sslCacheFlushSize > 0) { sessionCacheManager = folly::make_unique<SSLSessionCacheManager>( cacheOptions.maxSSLCacheSize, cacheOptions.sslCacheFlushSize, sslCtx.get(), vipAddress, commonName, eventBase_, stats_, externalCache); } // - end - SSL session cache config std::unique_ptr<TLSTicketKeyManager> ticketManager = createTicketManagerHelper(sslCtx, ticketSeeds, ctxConfig, stats_); // finalize sslCtx setup by the individual features supported by openssl ctxSetupByOpensslFeature(sslCtx, ctxConfig); try { insert(sslCtx, std::move(sessionCacheManager), std::move(ticketManager), ctxConfig.isDefault); } catch (const std::exception& ex) { string msg = folly::to<string>("Error adding certificate : ", folly::exceptionStr(ex)); LOG(ERROR) << msg; throw std::runtime_error(msg); } }