#ifdef HAVE_CONFIG_H #include "dgconfig.h" #endif #ifdef __SSLMITM #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "openssl/bio.h" #include "openssl/ssl.h" #include "openssl/err.h" #include "CertificateAuthority.hpp" #include "OptionContainer.hpp" extern OptionContainer o; extern thread_local std::string thread_id; void log_ssl_errors( const char *mess, const char *site) { if( o.log_ssl_errors ) { std::string tmess = thread_id; tmess += mess; syslog(LOG_ERR, tmess.c_str(), site); #ifdef DGDEBUG std::cout << thread_id << "SSL Error: " << mess << " at: " << site << std::endl; #endif unsigned long e; char buff[512]; while ((e = ERR_get_error())) { ERR_error_string(e, &buff[0]); syslog(LOG_ERR, "%s%s", thread_id.c_str(), buff ); #ifdef DGDEBUG std::cout << thread_id << "SSL Error: " << buff << " at: " << site << std::endl; #endif } } } CertificateAuthority::CertificateAuthority(const char *caCert, const char *caPrivKey, const char *certPrivKey, const char *certPath, time_t caStart, time_t caEnd) { FILE *fp; //load the ca cert fp = fopen(caCert, "r"); if (fp == NULL) { syslog(LOG_ERR, "%sCouldn't open ca certificate", thread_id.c_str()); exit(1); } _caCert = PEM_read_X509(fp, NULL, NULL, NULL); if (_caCert == NULL) { //syslog(LOG_ERR, "Couldn't load ca certificate"); log_ssl_errors("Couldn't load ca certificate from %s", caCert); //ERR_print_errors_fp(stderr); exit(1); } fclose(fp); //load the ca priv key fp = fopen(caPrivKey, "r"); if (fp == NULL) { syslog(LOG_ERR, "%sCouldn't open ca private key", thread_id.c_str()); exit(1); } _caPrivKey = PEM_read_PrivateKey(fp, NULL, NULL, NULL); if (_caPrivKey == NULL) { syslog(LOG_ERR, "%sCouldn't load ca private key", thread_id.c_str()); //ERR_print_errors_fp(stderr); exit(1); } fclose(fp); //load the priv key to use with generated certificates fp = fopen(certPrivKey, "r"); if (fp == NULL) { syslog(LOG_ERR, "%sCouldn't open certificate private key", thread_id.c_str()); exit(1); } _certPrivKey = PEM_read_PrivateKey(fp, NULL, NULL, NULL); if (_certPrivKey == NULL) { syslog(LOG_ERR, "%sCouldn't load certificate private key", thread_id.c_str()); //ERR_print_errors_fp(stderr); exit(1); } fclose(fp); //TODO should check this is a writable dir _certPath = certPath; _certPathLen = sizeof(certPath); // _certLinks = certLinks; _certLinks = certPath; // temp to check if this works //_ca_start = 1417872951; // 6th Dec 2014 //_ca_end = _ca_start + 315532800; // 6th Dec 2024 _ca_start = caStart; _ca_end = caEnd; } bool CertificateAuthority::getSerial(const char *commonname, struct ca_serial *caser) { //generate hash of hostname char cnhash[EVP_MAX_MD_SIZE]; unsigned int cnhashlen; // added to generate different serial number than previous versions // needs to be added as an option std::string sname(commonname ); sname += "B"; #ifdef DGDEBUG std::cout << thread_id << "Generating serial no for " << commonname << std::endl; #endif EVP_MD_CTX *mdctx; #if OPENSSL_VERSION_NUMBER < 0x10100000L EVP_MD_CTX mmdctx; mdctx = &mmdctx; #else mdctx = EVP_MD_CTX_new(); #endif const EVP_MD *md = EVP_md5(); EVP_MD_CTX_init(mdctx); bool failed = false; if (!failed && EVP_DigestInit_ex(mdctx, md, NULL) < 1) { failed = true; } if (!failed && EVP_DigestUpdate(mdctx, sname.c_str(), strlen(sname.c_str())) < 1) { failed = true; } if (!failed && EVP_DigestFinal_ex(mdctx, (unsigned char *)cnhash, &cnhashlen) < 1) { failed = true; } #if OPENSSL_VERSION_NUMBER < 0x10100000L EVP_MD_CTX_cleanup(mdctx); #else EVP_MD_CTX_free(mdctx); #endif if (failed) { return false; } //convert to asn1 to use as serial BIGNUM *bn = BN_bin2bn((const unsigned char *)cnhash, cnhashlen, NULL); if (bn == NULL) { return false; } char *dbg = BN_bn2hex(bn); #ifdef DGDEBUG if (dbg != NULL) { std::cout << thread_id << "Serial no is " << dbg << std::endl; } else { std::cout << thread_id << "bn2hex returned null instead of serial number" << std::endl; } #endif caser->charhex = dbg; caser->asn = BN_to_ASN1_INTEGER(bn, NULL); BN_free(bn); return true; } //write a certificate to disk being careful to avoid race conditions. //returns true if it already existed or false on error //common name (sh/c)ould be derived from the certificate but that would add to the complexity of the code bool CertificateAuthority::writeCertificate(const char *commonname, X509 *newCert, struct ca_serial *caser) { std::string path(caser->filename); std::string dirpath(caser->filepath); // make directory path int rc = mkpath(dirpath.c_str(), 0700); // only want e2g to have access to these dir if (rc != 0) { syslog(LOG_ERR, "%serror creating certificate sub-directory: %s", thread_id.c_str(),dirpath.c_str()); return false; } //open file struct flock fl; fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; fl.l_pid = getpid(); int fd = open(path.c_str(), O_RDWR | O_CREAT, S_IWUSR | S_IRUSR); //only e2g has access #ifdef DGDEBUG std::cout << thread_id << "certificate file is " << path << std::endl; #endif if (fd < 0) { syslog(LOG_ERR, "%serror opening new certificate", thread_id.c_str()); exit(1); } //lock file with blocking lock and see if its bigger than 0 bytes if (fcntl(fd, F_SETLKW, &fl) < 0) { close(fd); return false; } off_t pos = lseek(fd, 0, SEEK_END); //check if someone else created the file before we did (avoid the race condition) if (pos < 0) { #ifdef DGDEBUG std::cout << thread_id << "error seeking to find certificate size " << std::endl; #endif fl.l_type = F_UNLCK; fcntl(fd, F_SETLK, &fl); close(fd); return false; } else if (pos > 0) { //didnt get first lock so cert should be there now #ifdef DGDEBUG std::cout << thread_id << "didnt get first lock pos was " << pos << std::endl; #endif fl.l_type = F_UNLCK; fcntl(fd, F_SETLK, &fl); close(fd); return true; } //looks like we got the first lock so write the certificate //write the cert to a file #ifdef DGDEBUG std::cout << thread_id << "got first lock " << std::endl; #endif FILE *fp = fdopen(fd, "w"); if (fp == NULL) { return false; } if (PEM_write_X509(fp, newCert) < 1) { fclose(fp); return false; } if (fflush(fp) == EOF) { fclose(fp); return false; } if (fsync(fd) < 0) { fclose(fp); return false; } //unlock the file fl.l_type = F_UNLCK; fcntl(fd, F_SETLK, &fl); fclose(fp); close(fd); return true; } //generate a certificate for a given hostname X509 *CertificateAuthority::generateCertificate(const char *commonname, struct ca_serial *cser) { //create a blank cert ERR_clear_error(); X509 *newCert = X509_new(); if (newCert == NULL) { #ifdef DGDEBUG std::cout << thread_id << "new blank cert failed for " << commonname << std::endl; #endif log_ssl_errors("new blank cert failed for %s", commonname); return NULL; } ERR_clear_error(); if (X509_set_version(newCert, 2) < 1) { #ifdef DGDEBUG std::cout << thread_id << "set_version on cert failed for " << commonname << std::endl; #endif log_ssl_errors("set_version on cert failed for %s", commonname); X509_free(newCert); return NULL; } //set a serial on the cert ERR_clear_error(); if (X509_set_serialNumber(newCert, (cser->asn)) < 1) { #ifdef DGDEBUG std::cout << thread_id << "set_serialNumber on cert failed for " << commonname << std::endl; #endif log_ssl_errors("set_serialNumber on cert failed for %s", commonname); X509_free(newCert); return NULL; } //set valid from and expires dates // now from fixed date - should ensure regenerated certs are same and that servers in loadbalanced arrary give same cert if (!ASN1_TIME_set(X509_get_notBefore(newCert), _ca_start)) { #ifdef DGDEBUG std::cout << thread_id << "get_notBefore on cert failed for " << commonname << std::endl; #endif X509_free(newCert); return NULL; } if (!ASN1_TIME_set(X509_get_notAfter(newCert), _ca_end)) { #ifdef DGDEBUG std::cout << thread_id << "get_notAfter on cert failed for " << commonname << std::endl; #endif X509_free(newCert); return NULL; } //set the public key of the new cert //the private key data type also contains the pub key which is used below. ERR_clear_error(); if (X509_set_pubkey(newCert, _certPrivKey) < 1) { #ifdef DGDEBUG std::cout << thread_id << "set_pubkey on cert failed for " << commonname << std::endl; #endif log_ssl_errors("set_pubkey on cert failed for %s", commonname); X509_free(newCert); return NULL; } //create a name section ERR_clear_error(); X509_NAME *name = X509_get_subject_name(newCert); if (name == NULL) { #ifdef DGDEBUG std::cout << thread_id << "get_subject_name on cert failed for " << commonname << std::endl; #endif log_ssl_errors("get_subject_name on cert failed for %s", commonname); X509_free(newCert); return NULL; } //add the cn of the site we want a cert for the destination ERR_clear_error(); int rc = X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)commonname, -1, -1, 0); if (rc < 1) { #ifdef DGDEBUG std::cout << thread_id << "NAME_add_entry_by_txt on cert failed for " << commonname << std::endl; #endif log_ssl_errors("NAME_add_entry_by_txt on cert failed for %s", commonname); // X509_NAME_free(name); X509_free(newCert); return NULL; } //set the issuer name of the cert to the cn of the ca ERR_clear_error(); X509_NAME *subjectName = X509_get_subject_name(_caCert); if (subjectName == NULL) { #ifdef DGDEBUG std::cout << thread_id << "get_subject_name on ca_cert failed for " << commonname << std::endl; #endif log_ssl_errors("get_subject_name on ca_cert failed for %s", commonname); X509_free(newCert); return NULL; } ERR_clear_error(); if (X509_set_issuer_name(newCert, subjectName) < 1) { #ifdef DGDEBUG std::cout << thread_id << "set_issuer_name on cert failed for " << commonname << std::endl; #endif log_ssl_errors("set_issuer_name on cert failed for %s", commonname); // X509_NAME_free(subjectName); X509_free(newCert); return NULL; } { String temp1 = "DNS:"; String temp2 = commonname; temp1 = temp1 + temp2; char *value = (char*) temp1.toCharArray(); if( !addExtension(newCert, NID_subject_alt_name, value)) log_ssl_errors("Error adding subjectAltName to the request", commonname); } //sign it using the ca ERR_clear_error(); if (!X509_sign(newCert, _caPrivKey, EVP_sha256())) { #ifdef DGDEBUG std::cout << thread_id << "X509_sign on cert failed for " << commonname << std::endl; #endif log_ssl_errors("X509_sign on cert failed for %s", commonname); X509_free(newCert); return NULL; } #ifdef DGDEBUG std::cout << thread_id << "certificate create " << name << std::endl; #endif return newCert; } //sets cert to the certificate for commonname //returns true if the cert was loaded from cache / false if it was generated bool CertificateAuthority::getServerCertificate(const char *commonname, X509 **cert, struct ca_serial *caser) { getSerial(commonname, caser); std::string filename(caser->charhex); // Generate directory path std::string subpath(filename.substr(0, 2) + '/' + filename.substr(2, 2) + '/' + filename.substr(4, 2) + '/'); std::string filepath(_certLinks + subpath); std::string path(_certLinks + subpath + filename.substr(6)); caser->filepath = strdup(filepath.c_str()); caser->filename = strdup(path.c_str()); #ifdef DGDEBUG std::cout << thread_id << "looking for cert " << path << std::endl; #endif //check to see if there is a symlink to the file // std::string path(_certLinks + filename); FILE *link = fopen(path.c_str(), "r"); if (link != NULL) { #ifdef DGDEBUG std::cout << thread_id << "Certificate found" << std::endl; #endif //if there was then the certificate has already been created *cert = PEM_read_X509(link, NULL, NULL, NULL); fclose(link); //dont need to check the return as this returns null if it couldnt load a cert return true; } else { #ifdef DGDEBUG std::cout << thread_id << "Certificate not found. Creating one" << std::endl; #endif //generate a certificate *cert = generateCertificate(commonname, caser); return false; } } EVP_PKEY *CertificateAuthority::getServerPkey() { #if OPENSSL_VERSION_NUMBER < 0x10100000L //openssl is missing a EVP_PKEY_dup function so just up the ref count //see http://www.mail-archive.com/openssl-users@openssl.org/msg17614.html CRYPTO_add(&_certPrivKey->references, 1, CRYPTO_LOCK_EVP_PKEY); #else EVP_PKEY_up_ref(_certPrivKey); #endif return _certPrivKey; } int CertificateAuthority::do_mkdir(const char *path, mode_t mode) { struct stat st; int status = 0; if (stat(path, &st) != 0) { /* Directory does not exist. EEXIST for race condition */ if (mkdir(path, mode) != 0 && errno != EEXIST) status = -1; } else if (!S_ISDIR(st.st_mode)) { errno = ENOTDIR; status = -1; } return (status); } // mkpath - ensure all sub-directories in path exist int CertificateAuthority::mkpath(const char *path, mode_t mode) { char *pp; char *sp; int status; char *copypath = strdup(path); status = 0; pp = copypath + _certPathLen; //start checking within generated cert directory while (status == 0 && (sp = strchr(pp, '/')) != 0) { if (sp != pp) { *sp = '\0'; status = do_mkdir(copypath, mode); *sp = '/'; } pp = sp + 1; } if (status == 0) status = do_mkdir(path, mode); free(copypath); return (status); } bool CertificateAuthority::free_ca_serial(struct ca_serial *cs) { if (cs->asn != NULL) ASN1_INTEGER_free(cs->asn); if (cs->charhex != NULL) OPENSSL_free(cs->charhex); // free(cs->charhex); if (cs->filepath != NULL) free(cs->filepath); if (cs->filename != NULL) free(cs->filename); return true; } CertificateAuthority::~CertificateAuthority() { if (_caCert) X509_free(_caCert); if (_caPrivKey) EVP_PKEY_free(_caPrivKey); if (_certPrivKey) EVP_PKEY_free(_certPrivKey); } bool CertificateAuthority::addExtension(X509 *cert, int nid, char *value) { X509_EXTENSION *ex = NULL; ex = X509V3_EXT_conf_nid(NULL,NULL , nid, value); int result = X509_add_ext(cert, ex, -1); X509_EXTENSION_free(ex); return (result > 0) ? true : false; } #endif //__SSLMITM