poco/NetSSL_Win/src/X509Certificate.cpp
Günter Obiltschnig c2255c1c62 fixed warnings
2020-02-04 13:21:25 +01:00

569 lines
14 KiB
C++

//
// X509Certificate.cpp
//
// Library: NetSSL_Win
// Package: Certificate
// Module: X509Certificate
//
// Copyright (c) 2006-2014, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#include "Poco/Net/X509Certificate.h"
#include "Poco/Net/SSLException.h"
#include "Poco/StreamCopier.h"
#include "Poco/String.h"
#include "Poco/DateTimeParser.h"
#include "Poco/Base64Encoder.h"
#include "Poco/Base64Decoder.h"
#include "Poco/File.h"
#include "Poco/FileStream.h"
#include "Poco/MemoryStream.h"
#include "Poco/Buffer.h"
#include "Poco/UnicodeConverter.h"
#include "Poco/Format.h"
#include "Poco/RegularExpression.h"
#include "Poco/Net/DNS.h"
namespace Poco {
namespace Net {
X509Certificate::X509Certificate(const std::string& path):
_pCert(0)
{
importCertificate(path);
init();
}
X509Certificate::X509Certificate(std::istream& istr):
_pCert(0)
{
importCertificate(istr);
init();
}
X509Certificate::X509Certificate(const std::string& certName, const std::string& certStoreName, bool useMachineStore):
_pCert(0)
{
loadCertificate(certName, certStoreName, useMachineStore);
init();
}
X509Certificate::X509Certificate(PCCERT_CONTEXT pCert):
_pCert(pCert)
{
poco_check_ptr(_pCert);
init();
}
X509Certificate::X509Certificate(const X509Certificate& cert):
_issuerName(cert._issuerName),
_subjectName(cert._subjectName),
_pCert(cert._pCert)
{
_pCert = CertDuplicateCertificateContext(_pCert);
}
X509Certificate::X509Certificate(X509Certificate&& cert) noexcept:
_issuerName(std::move(cert._issuerName)),
_subjectName(std::move(cert._subjectName)),
_pCert(cert._pCert)
{
cert._pCert = nullptr;
}
X509Certificate::X509Certificate(PCCERT_CONTEXT pCert, bool shared):
_pCert(pCert)
{
poco_check_ptr(_pCert);
if (shared)
{
_pCert = CertDuplicateCertificateContext(_pCert);
}
init();
}
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);
_pCert = cert._pCert; cert._pCert = nullptr;
return *this;
}
void X509Certificate::swap(X509Certificate& cert)
{
using std::swap;
swap(cert._issuerName, _issuerName);
swap(cert._subjectName, _subjectName);
swap(cert._pCert, _pCert);
}
X509Certificate::~X509Certificate()
{
if (_pCert) CertFreeCertificateContext(_pCert);
}
void X509Certificate::init()
{
std::string name = issuerName(NID_COUNTRY);
if (!name.empty())
{
_issuerName += "/C=";
_issuerName += name;
}
name = issuerName(NID_STATE_OR_PROVINCE);
if (!name.empty())
{
_issuerName += "/ST=";
_issuerName += name;
}
name = issuerName(NID_LOCALITY_NAME);
if (!name.empty())
{
_issuerName += "/L=";
_issuerName += name;
}
name = issuerName(NID_ORGANIZATION_NAME);
if (!name.empty())
{
_issuerName += "/O=";
_issuerName += name;
}
name = issuerName(NID_ORGANIZATION_UNIT_NAME);
if (!name.empty())
{
_issuerName += "/OU=";
_issuerName += name;
}
name = issuerName(NID_COMMON_NAME);
if (!name.empty())
{
_issuerName += "/CN=";
_issuerName += name;
}
name = subjectName(NID_COUNTRY);
if (!name.empty())
{
_subjectName += "/C=";
_subjectName += name;
}
name = subjectName(NID_STATE_OR_PROVINCE);
if (!name.empty())
{
_subjectName += "/ST=";
_subjectName += name;
}
name = subjectName(NID_LOCALITY_NAME);
if (!name.empty())
{
_subjectName += "/L=";
_subjectName += name;
}
name = subjectName(NID_ORGANIZATION_NAME);
if (!name.empty())
{
_subjectName += "/O=";
_subjectName += name;
}
name = subjectName(NID_ORGANIZATION_UNIT_NAME);
if (!name.empty())
{
_subjectName += "/OU=";
_subjectName += name;
}
name = subjectName(NID_COMMON_NAME);
if (!name.empty())
{
_subjectName += "/CN=";
_subjectName += name;
}}
std::string X509Certificate::commonName() const
{
return subjectName(NID_COMMON_NAME);
}
std::string X509Certificate::issuerName(NID nid) const
{
std::string result;
DWORD size = CertGetNameStringW(_pCert, CERT_NAME_ATTR_TYPE, CERT_NAME_ISSUER_FLAG, nid2oid(nid), 0, 0);
Poco::Buffer<wchar_t> data(size);
CertGetNameStringW(_pCert, CERT_NAME_ATTR_TYPE, CERT_NAME_ISSUER_FLAG, nid2oid(nid), data.begin(), size);
Poco::UnicodeConverter::convert(data.begin(), result);
return result;
}
std::string X509Certificate::subjectName(NID nid) const
{
std::string result;
DWORD size = CertGetNameStringW(_pCert, CERT_NAME_ATTR_TYPE, 0, nid2oid(nid), 0, 0);
Poco::Buffer<wchar_t> data(size);
CertGetNameStringW(_pCert, CERT_NAME_ATTR_TYPE, 0, nid2oid(nid), data.begin(), size);
Poco::UnicodeConverter::convert(data.begin(), result);
return result;
}
void X509Certificate::extractNames(std::string& cmnName, std::set<std::string>& domainNames) const
{
domainNames.clear();
cmnName = commonName();
PCERT_EXTENSION pExt = _pCert->pCertInfo->rgExtension;
for (int i = 0; i < _pCert->pCertInfo->cExtension; i++, pExt++)
{
if (std::strcmp(pExt->pszObjId, szOID_SUBJECT_ALT_NAME2) == 0)
{
DWORD flags(0);
#if defined(CRYPT_DECODE_ENABLE_PUNYCODE_FLAG)
flags |= CRYPT_DECODE_ENABLE_PUNYCODE_FLAG;
#endif
Poco::Buffer<char> buffer(256);
DWORD bufferSize = static_cast<DWORD>(buffer.sizeBytes());
BOOL rc = CryptDecodeObjectEx(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
pExt->pszObjId,
pExt->Value.pbData,
pExt->Value.cbData,
flags,
0,
buffer.begin(),
&bufferSize);
if (!rc && GetLastError() == ERROR_MORE_DATA)
{
buffer.resize(bufferSize);
rc = CryptDecodeObjectEx(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
pExt->pszObjId,
pExt->Value.pbData,
pExt->Value.cbData,
flags,
0,
buffer.begin(),
&bufferSize);
}
if (rc)
{
PCERT_ALT_NAME_INFO pNameInfo = reinterpret_cast<PCERT_ALT_NAME_INFO>(buffer.begin());
for (int i = 0; i < pNameInfo->cAltEntry; i++)
{
std::wstring waltName(pNameInfo->rgAltEntry[i].pwszDNSName);
std::string altName;
Poco::UnicodeConverter::toUTF8(waltName, altName);
domainNames.insert(altName);
}
}
}
}
if (!cmnName.empty() && domainNames.empty())
{
domainNames.insert(cmnName);
}
}
Poco::DateTime X509Certificate::validFrom() const
{
Poco::Timestamp ts = Poco::Timestamp::fromFileTimeNP(_pCert->pCertInfo->NotBefore.dwLowDateTime, _pCert->pCertInfo->NotBefore.dwHighDateTime);
return Poco::DateTime(ts);
}
Poco::DateTime X509Certificate::expiresOn() const
{
Poco::Timestamp ts = Poco::Timestamp::fromFileTimeNP(_pCert->pCertInfo->NotAfter.dwLowDateTime, _pCert->pCertInfo->NotAfter.dwHighDateTime);
return Poco::DateTime(ts);
}
bool X509Certificate::issuedBy(const X509Certificate& issuerCertificate) const
{
CERT_CHAIN_PARA chainPara;
PCCERT_CHAIN_CONTEXT pChainContext = 0;
std::memset(&chainPara, 0, sizeof(chainPara));
chainPara.cbSize = sizeof(chainPara);
if (!CertGetCertificateChain(
NULL,
_pCert,
NULL,
NULL,
&chainPara,
0,
NULL,
&pChainContext))
{
throw SSLException("Cannot get certificate chain", subjectName(), GetLastError());
}
bool result = false;
for (DWORD i = 0; i < pChainContext->cChain && !result; i++)
{
for (DWORD k = 0; k < pChainContext->rgpChain[i]->cElement && !result; k++)
{
PCCERT_CONTEXT pChainCert = pChainContext->rgpChain[i]->rgpElement[k]->pCertContext;
if (CertCompareCertificate(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, issuerCertificate.system()->pCertInfo, pChainCert->pCertInfo))
{
if (CertComparePublicKeyInfo(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &issuerCertificate.system()->pCertInfo->SubjectPublicKeyInfo, &pChainCert->pCertInfo->SubjectPublicKeyInfo))
{
result = true;
}
}
}
}
CertFreeCertificateChain(pChainContext);
return result;
}
void* X509Certificate::nid2oid(NID nid)
{
const char* result = 0;
switch (nid)
{
case NID_COMMON_NAME:
result = szOID_COMMON_NAME;
break;
case NID_COUNTRY:
result = szOID_COUNTRY_NAME;
break;
case NID_LOCALITY_NAME:
result = szOID_LOCALITY_NAME;
break;
case NID_STATE_OR_PROVINCE:
result = szOID_STATE_OR_PROVINCE_NAME;
break;
case NID_ORGANIZATION_NAME:
result = szOID_ORGANIZATION_NAME;
break;
case NID_ORGANIZATION_UNIT_NAME:
result = szOID_ORGANIZATIONAL_UNIT_NAME;
break;
default:
poco_bugcheck();
result = "";
break;
}
return const_cast<char*>(result);
}
void X509Certificate::loadCertificate(const std::string& certName, const std::string& certStoreName, bool useMachineStore)
{
std::wstring wcertStoreName;
Poco::UnicodeConverter::convert(certStoreName, wcertStoreName);
HCERTSTORE hCertStore;
if (useMachineStore)
hCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE, wcertStoreName.c_str());
else
hCertStore = CertOpenSystemStoreW(0, wcertStoreName.c_str());
if (!hCertStore) throw CertificateException("Failed to open certificate store", certStoreName, GetLastError());
CERT_RDN_ATTR cert_rdn_attr;
cert_rdn_attr.pszObjId = szOID_COMMON_NAME;
cert_rdn_attr.dwValueType = CERT_RDN_ANY_TYPE;
cert_rdn_attr.Value.cbData = static_cast<DWORD>(certName.size());
cert_rdn_attr.Value.pbData = reinterpret_cast<BYTE*>(const_cast<char*>(certName.c_str()));
CERT_RDN cert_rdn;
cert_rdn.cRDNAttr = 1;
cert_rdn.rgRDNAttr = &cert_rdn_attr;
_pCert = CertFindCertificateInStore(hCertStore, X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_ATTR, &cert_rdn, NULL);
if (!_pCert)
{
CertCloseStore(hCertStore, 0);
throw NoCertificateException(Poco::format("Failed to find certificate %s in store %s", certName, certStoreName));
}
CertCloseStore(hCertStore, 0);
}
void X509Certificate::importCertificate(const std::string& certPath)
{
Poco::File certFile(certPath);
if (!certFile.exists()) throw Poco::FileNotFoundException(certPath);
Poco::File::FileSize size = certFile.getSize();
if (size < 32) throw Poco::DataFormatException("certificate file too small", certPath);
Poco::Buffer<char> buffer(static_cast<std::size_t>(size));
Poco::FileInputStream istr(certPath);
istr.read(buffer.begin(), buffer.size());
if (istr.gcount() != size) throw Poco::IOException("error reading certificate file");
importCertificate(buffer.begin(), buffer.size());
}
void X509Certificate::importCertificate(std::istream& istr)
{
std::string data;
Poco::StreamCopier::copyToString(istr, data);
if (!data.empty())
{
importCertificate(data.data(), data.size());
}
else throw Poco::IOException("failed to read certificate from stream");
}
void X509Certificate::importCertificate(const char* pBuffer, std::size_t size)
{
if (std::memcmp(pBuffer, "-----BEGIN CERTIFICATE-----", 27) == 0)
importPEMCertificate(pBuffer + 27, size - 27);
else
importDERCertificate(pBuffer, size);
}
void X509Certificate::importPEMCertificate(const char* pBuffer, std::size_t size)
{
Poco::Buffer<char> derBuffer(size);
std::size_t derSize = 0;
const char* pemBegin = pBuffer;
const char* pemEnd = pemBegin + (size - 25);
while (pemEnd > pemBegin && std::memcmp(pemEnd, "-----END CERTIFICATE-----", 25) != 0) --pemEnd;
if (pemEnd == pemBegin) throw Poco::DataFormatException("Not a valid PEM file - end marker missing");
Poco::MemoryInputStream istr(pemBegin, pemEnd - pemBegin);
Poco::Base64Decoder dec(istr);
char* derBegin = derBuffer.begin();
char* derEnd = derBegin;
int ch = dec.get();
while (ch != -1)
{
*derEnd++ = static_cast<char>(ch);
ch = dec.get();
}
importDERCertificate(derBegin, derEnd - derBegin);
}
void X509Certificate::importDERCertificate(const char* pBuffer, std::size_t size)
{
_pCert = CertCreateCertificateContext(X509_ASN_ENCODING, reinterpret_cast<const BYTE*>(pBuffer), static_cast<DWORD>(size));
if (!_pCert)
{
throw Poco::DataFormatException("Failed to load certificate from file", GetLastError());
}
}
bool X509Certificate::verify(const std::string& hostName) const
{
return verify(*this, hostName);
}
bool X509Certificate::verify(const Poco::Net::X509Certificate& certificate, const std::string& hostName)
{
std::string commonName;
std::set<std::string> dnsNames;
certificate.extractNames(commonName, dnsNames);
if (!commonName.empty()) dnsNames.insert(commonName);
bool ok = (dnsNames.find(hostName) != dnsNames.end());
if (!ok)
{
for (std::set<std::string>::const_iterator it = dnsNames.begin(); !ok && it != dnsNames.end(); ++it)
{
try
{
// two cases: strData contains wildcards or not
if (containsWildcards(*it))
{
// a compare by IPAddress is not possible with wildcards
// only allow compare by name
ok = matchWildcard(*it, hostName);
}
else
{
// it depends on hostName if we compare by IP or by alias
IPAddress ip;
if (IPAddress::tryParse(hostName, ip))
{
// compare by IP
const HostEntry& heData = DNS::resolve(*it);
const HostEntry::AddressList& addr = heData.addresses();
HostEntry::AddressList::const_iterator it = addr.begin();
HostEntry::AddressList::const_iterator itEnd = addr.end();
for (; it != itEnd && !ok; ++it)
{
ok = (*it == ip);
}
}
else
{
ok = Poco::icompare(*it, hostName) == 0;
}
}
}
catch (NoAddressFoundException&)
{
}
catch (HostNotFoundException&)
{
}
}
}
return ok;
}
bool X509Certificate::containsWildcards(const std::string& commonName)
{
return (commonName.find('*') != std::string::npos || commonName.find('?') != std::string::npos);
}
bool X509Certificate::matchWildcard(const std::string& wildcard, const std::string& hostName)
{
// fix wildcards
std::string wildcardExpr("^");
wildcardExpr += Poco::replace(wildcard, ".", "\\.");
Poco::replaceInPlace(wildcardExpr, "*", ".*");
Poco::replaceInPlace(wildcardExpr, "..*", ".*");
Poco::replaceInPlace(wildcardExpr, "?", ".?");
Poco::replaceInPlace(wildcardExpr, "..?", ".?");
wildcardExpr += "$";
Poco::RegularExpression expr(wildcardExpr, Poco::RegularExpression::RE_CASELESS);
return expr.match(hostName);
}
} } // namespace Poco::Net