// // X509Certificate.cpp // // Library: Crypto // Package: Certificate // Module: X509Certificate // // Copyright (c) 2006-2009, Applied Informatics Software Engineering GmbH. // and Contributors. // // SPDX-License-Identifier: BSL-1.0 // #include "Poco/Crypto/X509Certificate.h" #include "Poco/Crypto/CryptoException.h" #include "Poco/StreamCopier.h" #include "Poco/String.h" #include "Poco/DateTimeParser.h" #include "Poco/Format.h" #include #include #ifdef _WIN32 // fix for WIN32 header conflict #undef X509_NAME #endif #include #include #include #include #if OPENSSL_VERSION_NUMBER < 0x10100000L #define ASN1_STRING_get0_data ASN1_STRING_data #define X509_get0_notBefore X509_get_notBefore #define X509_get0_notAfter X509_get_notAfter #endif namespace Poco { namespace Crypto { X509Certificate::X509Certificate(std::istream& istr): _pCert(0) { load(istr); } X509Certificate::X509Certificate(const std::string& path): _pCert(0) { load(path); } X509Certificate::X509Certificate(X509* pCert): _pCert(pCert) { poco_check_ptr(_pCert); init(); } X509Certificate::X509Certificate(X509* pCert, bool shared): _pCert(pCert) { poco_check_ptr(_pCert); if (shared) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L X509_up_ref(_pCert); #else _pCert->references++; #endif } init(); } X509Certificate::X509Certificate(const X509Certificate& cert): _issuerName(cert._issuerName), _subjectName(cert._subjectName), _serialNumber(cert._serialNumber), _pCert(cert._pCert) { _pCert = X509_dup(_pCert); } X509Certificate::X509Certificate(X509Certificate&& cert) noexcept: _issuerName(std::move(cert._issuerName)), _subjectName(std::move(cert._subjectName)), _serialNumber(std::move(cert._serialNumber)), _pCert(cert._pCert) { cert._pCert = nullptr; } X509Certificate& X509Certificate::operator = (const X509Certificate& cert) { X509Certificate tmp(cert); swap(tmp); return *this; } X509Certificate& X509Certificate::operator = (X509Certificate&& cert) noexcept { _issuerName = std::move(cert._issuerName); _subjectName = std::move(cert._subjectName); _serialNumber = std::move(cert._serialNumber); if (_pCert) X509_free(_pCert); _pCert = cert._pCert; cert._pCert = nullptr; return *this; } void X509Certificate::swap(X509Certificate& cert) noexcept { using std::swap; swap(cert._issuerName, _issuerName); swap(cert._subjectName, _subjectName); swap(cert._serialNumber, _serialNumber); swap(cert._pCert, _pCert); } X509Certificate::~X509Certificate() { if (_pCert) X509_free(_pCert); } void X509Certificate::load(std::istream& istr) { poco_assert (!_pCert); std::stringstream certStream; Poco::StreamCopier::copyStream(istr, certStream); std::string cert = certStream.str(); BIO *pBIO = BIO_new_mem_buf(const_cast(cert.data()), static_cast(cert.size())); if (!pBIO) throw Poco::IOException("Cannot create BIO for reading certificate"); _pCert = PEM_read_bio_X509(pBIO, 0, 0, 0); BIO_free(pBIO); if (!_pCert) throw Poco::IOException("Failed to load certificate from stream"); init(); } void X509Certificate::load(const std::string& path) { poco_assert (!_pCert); BIO *pBIO = BIO_new(BIO_s_file()); if (!pBIO) throw Poco::IOException("Cannot create BIO for reading certificate file", path); if (!BIO_read_filename(pBIO, path.c_str())) { BIO_free(pBIO); throw Poco::OpenFileException("Cannot open certificate file for reading", path); } _pCert = PEM_read_bio_X509(pBIO, 0, 0, 0); BIO_free(pBIO); if (!_pCert) throw Poco::ReadFileException("Faild to load certificate from", path); init(); } void X509Certificate::save(std::ostream& stream) const { BIO *pBIO = BIO_new(BIO_s_mem()); if (!pBIO) throw Poco::IOException("Cannot create BIO for writing certificate"); try { if (!PEM_write_bio_X509(pBIO, _pCert)) throw Poco::IOException("Failed to write certificate to stream"); char *pData; long size; size = BIO_get_mem_data(pBIO, &pData); stream.write(pData, size); } catch (...) { BIO_free(pBIO); throw; } BIO_free(pBIO); } void X509Certificate::save(const std::string& path) const { BIO *pBIO = BIO_new(BIO_s_file()); if (!pBIO) throw Poco::IOException("Cannot create BIO for reading certificate file", path); if (!BIO_write_filename(pBIO, const_cast(path.c_str()))) { BIO_free(pBIO); throw Poco::CreateFileException("Cannot create certificate file", path); } try { if (!PEM_write_bio_X509(pBIO, _pCert)) throw Poco::WriteFileException("Failed to write certificate to file", path); } catch (...) { BIO_free(pBIO); throw; } BIO_free(pBIO); } std::string _X509_NAME_oneline_utf8(X509_NAME *name) { BIO * bio_out = BIO_new(BIO_s_mem()); X509_NAME_print_ex(bio_out, name, 0, (ASN1_STRFLGS_RFC2253 | XN_FLAG_SEP_COMMA_PLUS | XN_FLAG_FN_SN | XN_FLAG_DUMP_UNKNOWN_FIELDS) & ~ASN1_STRFLGS_ESC_MSB); BUF_MEM *bio_buf; BIO_get_mem_ptr(bio_out, &bio_buf); std::string line = std::string(bio_buf->data, bio_buf->length); BIO_free(bio_out); return line; } void X509Certificate::init() { _issuerName = _X509_NAME_oneline_utf8(X509_get_issuer_name(_pCert)); _subjectName = _X509_NAME_oneline_utf8(X509_get_subject_name(_pCert)); BIGNUM* pBN = ASN1_INTEGER_to_BN(X509_get_serialNumber(const_cast(_pCert)), 0); if (pBN) { char* pSN = BN_bn2hex(pBN); if (pSN) { _serialNumber = pSN; OPENSSL_free(pSN); } BN_free(pBN); } } std::string X509Certificate::commonName() const { return subjectName(NID_COMMON_NAME); } std::string X509Certificate::issuerName(NID nid) const { if (X509_NAME* issuer = X509_get_issuer_name(_pCert)) { char buffer[NAME_BUFFER_SIZE]; if (X509_NAME_get_text_by_NID(issuer, nid, buffer, sizeof(buffer)) >= 0) return std::string(buffer); } return std::string(); } std::string X509Certificate::subjectName(NID nid) const { if (X509_NAME* subj = X509_get_subject_name(_pCert)) { char buffer[NAME_BUFFER_SIZE]; if (X509_NAME_get_text_by_NID(subj, nid, buffer, sizeof(buffer)) >= 0) return std::string(buffer); } return std::string(); } void X509Certificate::extractNames(std::string& cmnName, std::set& domainNames) const { domainNames.clear(); if (STACK_OF(GENERAL_NAME)* names = static_cast(X509_get_ext_d2i(_pCert, NID_subject_alt_name, 0, 0))) { for (int i = 0; i < sk_GENERAL_NAME_num(names); ++i) { const GENERAL_NAME* name = sk_GENERAL_NAME_value(names, i); if (name->type == GEN_DNS) { const char* data = reinterpret_cast(ASN1_STRING_get0_data(name->d.ia5)); std::size_t len = ASN1_STRING_length(name->d.ia5); domainNames.insert(std::string(data, len)); } } GENERAL_NAMES_free(names); } cmnName = commonName(); if (!cmnName.empty() && domainNames.empty()) { domainNames.insert(cmnName); } } Poco::DateTime X509Certificate::validFrom() const { const ASN1_TIME* certTime = X509_get0_notBefore(_pCert); std::string dateTime(reinterpret_cast(certTime->data)); int tzd; if (certTime->type == V_ASN1_UTCTIME) { return DateTimeParser::parse("%y%m%d%H%M%S", dateTime, tzd); } else if (certTime->type == V_ASN1_GENERALIZEDTIME) { return DateTimeParser::parse("%Y%m%d%H%M%S", dateTime, tzd); } else { throw NotImplementedException("Unsupported date/time format in notBefore"); } } Poco::DateTime X509Certificate::expiresOn() const { const ASN1_TIME* certTime = X509_get0_notAfter(_pCert); std::string dateTime(reinterpret_cast(certTime->data)); int tzd; if (certTime->type == V_ASN1_UTCTIME) { return DateTimeParser::parse("%y%m%d%H%M%S", dateTime, tzd); } else if (certTime->type == V_ASN1_GENERALIZEDTIME) { return DateTimeParser::parse("%Y%m%d%H%M%S", dateTime, tzd); } else { throw NotImplementedException("Unsupported date/time format in notBefore"); } } Poco::DigestEngine::Digest X509Certificate::fingerprint(const std::string& algorithm) const { unsigned char buffer[EVP_MAX_MD_SIZE]; unsigned int length; const EVP_MD* md = EVP_get_digestbyname(algorithm.c_str()); if (!md) throw Poco::InvalidArgumentException(algorithm); if (X509_digest(_pCert, md, buffer, &length)) { return Poco::DigestEngine::Digest(buffer, buffer + length); } else { throw OpenSSLException("failed to compute fingerprint"); } } bool X509Certificate::issuedBy(const X509Certificate& issuerCertificate) const { X509* pCert = const_cast(_pCert); X509* pIssuerCert = const_cast(issuerCertificate.certificate()); EVP_PKEY* pIssuerPublicKey = X509_get_pubkey(pIssuerCert); if (!pIssuerPublicKey) throw Poco::InvalidArgumentException("Issuer certificate has no public key"); int rc = X509_verify(pCert, pIssuerPublicKey); EVP_PKEY_free(pIssuerPublicKey); return rc == 1; } bool X509Certificate::equals(const X509Certificate& otherCertificate) const { X509* pCert = const_cast(_pCert); X509* pOtherCert = const_cast(otherCertificate.certificate()); return X509_cmp(pCert, pOtherCert) == 0; } std::string X509Certificate::signatureAlgorithm() const { int sigNID = NID_undef; #if (OPENSSL_VERSION_NUMBER >= 0x1010000fL) sigNID = X509_get_signature_nid(_pCert); #else poco_check_ptr(_pCert->sig_alg); sigNID = OBJ_obj2nid(_pCert->sig_alg->algorithm); #endif if (sigNID != NID_undef) { const char* pAlgName = OBJ_nid2ln(sigNID); if (pAlgName) return std::string(pAlgName); else throw OpenSSLException(Poco::format("X509Certificate::" "signatureAlgorithm(): OBJ_nid2ln(%d)", sigNID)); } else throw NotFoundException("X509Certificate::signatureAlgorithm()"); return ""; } X509Certificate::List X509Certificate::readPEM(const std::string& pemFileName) { List caCertList; BIO* pBIO = BIO_new_file(pemFileName.c_str(), "r"); if (pBIO == NULL) throw OpenFileException(Poco::format("X509Certificate::readPEM(%s)", pemFileName)); X509* x = PEM_read_bio_X509(pBIO, NULL, 0, NULL); if (!x) { BIO_free(pBIO); throw OpenSSLException(Poco::format("X509Certificate::readPEM(%s)", pemFileName)); } while (x) { caCertList.push_back(X509Certificate(x)); x = PEM_read_bio_X509(pBIO, NULL, 0, NULL); } BIO_free(pBIO); return caCertList; } void X509Certificate::writePEM(const std::string& pemFileName, const List& list) { BIO* pBIO = BIO_new_file(pemFileName.c_str(), "a"); if (pBIO == NULL) throw OpenFileException(Poco::format("X509Certificate::writePEM(%s)", pemFileName)); List::const_iterator it = list.begin(); List::const_iterator end = list.end(); for (; it != end; ++it) { if (!PEM_write_bio_X509(pBIO, const_cast(it->certificate()))) { BIO_free(pBIO); throw OpenSSLException(Poco::format("X509Certificate::writePEM(%s)", pemFileName)); } } BIO_free(pBIO); } void X509Certificate::print(std::ostream& out) const { out << "subjectName: " << subjectName() << std::endl; out << "issuerName: " << issuerName() << std::endl; out << "commonName: " << commonName() << std::endl; out << "country: " << subjectName(X509Certificate::NID_COUNTRY) << std::endl; out << "localityName: " << subjectName(X509Certificate::NID_LOCALITY_NAME) << std::endl; out << "stateOrProvince: " << subjectName(X509Certificate::NID_STATE_OR_PROVINCE) << std::endl; out << "organizationName: " << subjectName(X509Certificate::NID_ORGANIZATION_NAME) << std::endl; out << "organizationUnitName: " << subjectName(X509Certificate::NID_ORGANIZATION_UNIT_NAME) << std::endl; out << "emailAddress: " << subjectName(X509Certificate::NID_PKCS9_EMAIL_ADDRESS) << std::endl; out << "serialNumber: " << subjectName(X509Certificate::NID_SERIAL_NUMBER) << std::endl; } } } // namespace Poco::Crypto