// // VMime library (http://www.vmime.org) // Copyright (C) 2002 Vincent Richard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 3 of // the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // // Linking this library statically or dynamically with other modules is making // a combined work based on this library. Thus, the terms and conditions of // the GNU General Public License cover the whole combination. // #include "vmime/config.hpp" #if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL #include #include #include #include #include "vmime/security/cert/openssl/X509Certificate_OpenSSL.hpp" #include "vmime/net/tls/openssl/OpenSSLInitializer.hpp" #include "vmime/utility/outputStreamByteArrayAdapter.hpp" #include "vmime/security/cert/certificateException.hpp" #include "vmime/security/cert/unsupportedCertificateTypeException.hpp" #include #include #include #include #include #include #ifdef _WIN32 # define strcasecmp _stricmp # define strncasecmp _strnicmp #endif namespace vmime { namespace security { namespace cert { static net::tls::OpenSSLInitializer::autoInitializer openSSLInitializer; #ifndef VMIME_BUILDING_DOC class monthMap { public: monthMap() { m_monthMap["jan"] = vmime::datetime::JAN; m_monthMap["feb"] = vmime::datetime::FEB; m_monthMap["mar"] = vmime::datetime::MAR; m_monthMap["apr"] = vmime::datetime::APR; m_monthMap["may"] = vmime::datetime::MAY; m_monthMap["jun"] = vmime::datetime::JUN; m_monthMap["jul"] = vmime::datetime::JUL; m_monthMap["aug"] = vmime::datetime::AUG; m_monthMap["sep"] = vmime::datetime::SEP; m_monthMap["oct"] = vmime::datetime::OCT; m_monthMap["nov"] = vmime::datetime::NOV; m_monthMap["dec"] = vmime::datetime::DEC; } int getMonth(vmime::string mstr) { std::transform(mstr.begin(), mstr.end(), mstr.begin(), ::tolower); std::map ::const_iterator c_it = m_monthMap.find(mstr); if (c_it != m_monthMap.end()) { return c_it->second; } return -1; } private: std::map m_monthMap; }; static monthMap sg_monthMap; struct OpenSSLX509CertificateInternalData { OpenSSLX509CertificateInternalData() { cert = 0; } ~OpenSSLX509CertificateInternalData() { if (cert) { X509_free(cert); } } X509* cert; }; // Workaround for i2v() taking either a const or a non-const 'method' on some platforms STACK_OF(CONF_VALUE)* call_i2v(const X509V3_EXT_METHOD* m, void* p1, STACK_OF(CONF_VALUE)* p2) { return m->i2v(m, p1, p2); } STACK_OF(CONF_VALUE)* call_i2v(X509V3_EXT_METHOD* m, void* p1, STACK_OF(CONF_VALUE)* p2) { return m->i2v(m, p1, p2); } #endif // VMIME_BUILDING_DOC X509Certificate_OpenSSL::X509Certificate_OpenSSL() : m_data(new OpenSSLX509CertificateInternalData) { } X509Certificate_OpenSSL::X509Certificate_OpenSSL(X509* cert) : m_data(new OpenSSLX509CertificateInternalData) { m_data->cert = X509_dup(cert); } X509Certificate_OpenSSL::X509Certificate_OpenSSL(const X509Certificate_OpenSSL&) : X509Certificate(), m_data(NULL) { // Not used } X509Certificate_OpenSSL::~X509Certificate_OpenSSL() { delete m_data; } void* X509Certificate_OpenSSL::getInternalData() { return &m_data->cert; } // static shared_ptr X509Certificate_OpenSSL::importInternal(X509* cert) { if (cert) { return make_shared (reinterpret_cast (cert)); } return null; } // static shared_ptr X509Certificate::import(utility::inputStream& is) { byteArray bytes; byte_t chunk[4096]; while (!is.eof()) { const size_t len = is.read(chunk, sizeof(chunk)); bytes.insert(bytes.end(), chunk, chunk + len); } return import(&bytes[0], bytes.size()); } // static shared_ptr X509Certificate::import( const byte_t* data, const size_t length ) { shared_ptr cert = make_shared (); BIO* membio = BIO_new_mem_buf(const_cast (data), static_cast (length)); if (!(cert->m_data->cert = d2i_X509_bio(membio, NULL)) /*!PEM_read_bio_X509(membio, &(cert->m_data->cert), 0, 0)*/) { BIO_vfree(membio); return null; } BIO_vfree(membio); return cert; } // static void X509Certificate::import( utility::inputStream& is, std::vector >& certs ) { byteArray bytes; byte_t chunk[4096]; while (!is.eof()) { const size_t len = is.read(chunk, sizeof(chunk)); bytes.insert(bytes.end(), chunk, chunk + len); } import(&bytes[0], bytes.size(), certs); } // static void X509Certificate::import( const byte_t* data, const size_t length, std::vector >& certs ) { BIO* membio = BIO_new_mem_buf(const_cast (data), static_cast (length)); shared_ptr cert = null; while (true) { cert = make_shared (); if (!PEM_read_bio_X509(membio, &(cert->m_data->cert), 0, 0)) { break; } certs.push_back(cert); } BIO_vfree(membio); } void X509Certificate_OpenSSL::write( utility::outputStream& os, const Format format ) const { BIO* membio = 0; long dataSize = 0; unsigned char* out = 0; if (format == FORMAT_DER) { if ((dataSize = i2d_X509(m_data->cert, &out)) < 0) { goto err; } os.write(reinterpret_cast (out), dataSize); os.flush(); OPENSSL_free(out); } else if (format == FORMAT_PEM) { membio = BIO_new(BIO_s_mem()); BIO_set_close(membio, BIO_CLOSE); if (!PEM_write_bio_X509(membio, m_data->cert)) { goto pem_err; } dataSize = BIO_get_mem_data(membio, &out); os.write(reinterpret_cast (out), dataSize); os.flush(); BIO_vfree(membio); } else { throw unsupportedCertificateTypeException("Unknown format"); } return; // #### Early Return #### pem_err: { if (membio) { BIO_vfree(membio); } } err: { char errstr[256]; long ec = ERR_get_error(); ERR_error_string(ec, errstr); throw certificateException("OpenSSLX509Certificate_OpenSSL::write exception - " + string(errstr)); } } const byteArray X509Certificate_OpenSSL::getSerialNumber() const { ASN1_INTEGER *serial = X509_get_serialNumber(m_data->cert); BIGNUM *bnser = ASN1_INTEGER_to_BN(serial, NULL); int n = BN_num_bytes(bnser); byte_t* outbuf = new byte_t[n]; BN_bn2bin(bnser, outbuf); byteArray ser(outbuf, outbuf + n); delete [] outbuf; BN_free(bnser); return ser; } bool X509Certificate_OpenSSL::checkIssuer(const shared_ptr & cert_) const { shared_ptr cert = dynamicCast (cert_); // Get issuer for this cert BIO *out; unsigned char *issuer; out = BIO_new(BIO_s_mem()); X509_NAME_print_ex(out, X509_get_issuer_name(m_data->cert), 0, XN_FLAG_RFC2253); long n = BIO_get_mem_data(out, &issuer); vmime::string thisIssuerName((char*)issuer, n); BIO_free(out); // Get subject of issuer unsigned char *subject; out = BIO_new(BIO_s_mem()); X509_NAME_print_ex(out, X509_get_subject_name(cert->m_data->cert), 0, XN_FLAG_RFC2253); n = BIO_get_mem_data(out, &subject); vmime::string subjOfIssuer((char*)subject, n); BIO_free(out); return subjOfIssuer == thisIssuerName; } bool X509Certificate_OpenSSL::verify(const shared_ptr & caCert_) const { shared_ptr caCert = dynamicCast (caCert_); // printf("ptr before cast is %p\n", caCert_.get()); // printf("ptr is %p\n", caCert.get()); bool verified = false; bool error = true; X509_STORE *store = X509_STORE_new(); if (store) { X509_STORE_CTX *verifyCtx = X509_STORE_CTX_new(); if (verifyCtx) { if (X509_STORE_add_cert(store, caCert->m_data->cert)) { X509_STORE_CTX_init(verifyCtx, store, m_data->cert, NULL); int ret = X509_verify_cert(verifyCtx); if (ret == 1) { verified = true; error = false; } else if (ret == 0) { verified = false; error = false; } //X509_verify_cert_error_string(vrfy_ctx->error) X509_STORE_CTX_free(verifyCtx); } } X509_STORE_free(store); } return verified && !error; } // static bool X509Certificate_OpenSSL::cnMatch(const char* cnBuf, const char* host) { // Right-to-left match, looking for a '*' wildcard const bool hasWildcard = (strlen(cnBuf) > 1 && cnBuf[0] == '*' && cnBuf[1] == '.'); const char* cnBufReverseEndPtr = (cnBuf + (hasWildcard ? 2 : 0)); const char* hostPtr = host + strlen(host); const char* cnPtr = cnBuf + strlen(cnBuf); bool matches = true; while (matches && --hostPtr >= host && --cnPtr >= cnBufReverseEndPtr) { matches = (toupper(*hostPtr) == toupper(*cnPtr)); } return matches; } bool X509Certificate_OpenSSL::verifyHostName( const string& hostname, std::vector * nonMatchingNames ) const { // First, check subject common name against hostname char CNBuffer[1024]; CNBuffer[sizeof(CNBuffer) - 1] = '\0'; X509_NAME* xname = X509_get_subject_name(m_data->cert); if (X509_NAME_get_text_by_NID(xname, NID_commonName, CNBuffer, sizeof(CNBuffer)) != -1) { if (cnMatch(CNBuffer, hostname.c_str())) { return true; } if (nonMatchingNames) { nonMatchingNames->push_back(CNBuffer); } } // Now, look in subject alternative names bool verify = false; STACK_OF(GENERAL_NAME)* altNames = static_cast (X509_get_ext_d2i(m_data->cert, NID_subject_alt_name, NULL, NULL)); if (altNames == NULL) { return false; } // Check each name within the extension for (int i = 0, n = sk_GENERAL_NAME_num(altNames) ; i < n ; ++i) { const GENERAL_NAME* currentName = sk_GENERAL_NAME_value(altNames, i); if (currentName->type == GEN_DNS) { // Current name is a DNS name, let's check it char *DNSName = (char *) ASN1_STRING_data(currentName->d.dNSName); // Make sure there isn't an embedded NUL character in the DNS name if (ASN1_STRING_length(currentName->d.dNSName) != strlen(DNSName)) { // Malformed certificate break; } if (cnMatch(DNSName, hostname.c_str())) { verify = true; break; } if (nonMatchingNames) { nonMatchingNames->push_back(DNSName); } } } sk_GENERAL_NAME_pop_free(altNames, GENERAL_NAME_free); return verify; } const datetime X509Certificate_OpenSSL::convertX509Date(void* time) const { char* buffer; BIO* out = BIO_new(BIO_s_mem()); BIO_set_close(out, BIO_CLOSE); ASN1_TIME* asn1_time = reinterpret_cast(time); ASN1_TIME_print(out, asn1_time); const long sz = BIO_get_mem_data(out, &buffer); char* dest = new char[sz + 1]; dest[sz] = 0; memcpy(dest, buffer, sz); vmime::string t(dest); BIO_free(out); delete [] dest; if (t.size() > 0) { char month[4] = {0}; char zone[4] = {0}; int day, hour, minute, second, year; int nrconv = sscanf(t.c_str(), "%s %2d %02d:%02d:%02d %d%s", month, &day, &hour, &minute, &second,&year,zone); if (nrconv >= 6) { return datetime(year, sg_monthMap.getMonth(vmime::string(month)), day, hour, minute, second); } } // let datetime try and parse it return datetime(t); } const datetime X509Certificate_OpenSSL::getActivationDate() const { return convertX509Date(X509_get_notBefore(m_data->cert)); } const datetime X509Certificate_OpenSSL::getExpirationDate() const { return convertX509Date(X509_get_notAfter(m_data->cert)); } const byteArray X509Certificate_OpenSSL::getFingerprint(const DigestAlgorithm algo) const { BIO *out; int j; unsigned int n; const EVP_MD *digest; unsigned char * fingerprint; unsigned char md[EVP_MAX_MD_SIZE]; switch (algo) { case DIGEST_MD5: digest = EVP_md5(); break; default: case DIGEST_SHA1: digest = EVP_sha1(); break; } out = BIO_new(BIO_s_mem()); BIO_set_close(out, BIO_CLOSE); if (X509_digest(m_data->cert, digest, md, &n)) { for (j = 0 ; j < (int) n ; j++) { BIO_printf(out, "%02X", md[j]); if (j + 1 != (int) n) BIO_printf(out, ":"); } } const long resultLen = BIO_get_mem_data(out, &fingerprint); unsigned char* result = new unsigned char[resultLen]; memcpy(result, fingerprint, resultLen); BIO_free(out); byteArray res; res.insert(res.end(), &result[0], &result[0] + resultLen); delete [] result; return res; } const byteArray X509Certificate_OpenSSL::getEncoded() const { byteArray bytes; utility::outputStreamByteArrayAdapter os(bytes); write(os, FORMAT_DER); return bytes; } const string X509Certificate_OpenSSL::getIssuerString() const { // Get issuer for this cert BIO* out = BIO_new(BIO_s_mem()); X509_NAME_print_ex(out, X509_get_issuer_name(m_data->cert), 0, XN_FLAG_RFC2253); unsigned char* issuer; const long n = BIO_get_mem_data(out, &issuer); vmime::string name(reinterpret_cast (issuer), n); BIO_free(out); return name; } const string X509Certificate_OpenSSL::getType() const { return "X.509"; } int X509Certificate_OpenSSL::getVersion() const { return (int) X509_get_version(m_data->cert); } bool X509Certificate_OpenSSL::equals(const shared_ptr & other) const { shared_ptr otherX509 = dynamicCast (other); if (!otherX509) { return false; } const byteArray fp1 = getFingerprint(DIGEST_MD5); const byteArray fp2 = otherX509->getFingerprint(DIGEST_MD5); return fp1 == fp2; } } // cert } // security } // vmime #endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL