mirror of
https://github.com/pocoproject/poco.git
synced 2025-10-29 20:59:45 +01:00
Added support for loading certificates and private key pairs from PKCS #12 files, as well as loading certificates (without private key) from PEM or DER files. Some code restructuring and cleanup.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
//
|
||||
// Context.cpp
|
||||
//
|
||||
// $Id: //poco/1.4/NetSSL_Schannel/src/Context.cpp#1 $
|
||||
// $Id$
|
||||
//
|
||||
// Library: NetSSL_Schannel
|
||||
// Library: NetSSL_Win
|
||||
// Package: SSLCore
|
||||
// Module: Context
|
||||
//
|
||||
@@ -17,7 +17,16 @@
|
||||
#include "Poco/Net/Context.h"
|
||||
#include "Poco/Net/SSLManager.h"
|
||||
#include "Poco/Net/SSLException.h"
|
||||
#include "Poco/Net/Utility.h"
|
||||
#include "Poco/UnicodeConverter.h"
|
||||
#include "Poco/Format.h"
|
||||
#include "Poco/File.h"
|
||||
#include "Poco/FileStream.h"
|
||||
#include "Poco/MemoryStream.h"
|
||||
#include "Poco/Base64Decoder.h"
|
||||
#include "Poco/Buffer.h"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
|
||||
namespace Poco {
|
||||
@@ -32,20 +41,54 @@ const std::string Context::CERT_STORE_USERDS("USERDS");
|
||||
|
||||
|
||||
Context::Context(Usage usage,
|
||||
const std::string& certName,
|
||||
const std::string& certNameOrPath,
|
||||
VerificationMode verMode,
|
||||
int options,
|
||||
const std::string& certStore):
|
||||
_usage(usage),
|
||||
_mode(verMode),
|
||||
_options(options),
|
||||
_certificateName(certName),
|
||||
_certificateStoreName(certStore),
|
||||
_certNameOrPath(certNameOrPath),
|
||||
_certStoreName(certStore),
|
||||
_hMemCertStore(0),
|
||||
_hCollectionCertStore(0),
|
||||
_hRootCertStore(0),
|
||||
_mutex()
|
||||
_hCertStore(0),
|
||||
_pCert(0),
|
||||
_securityFunctions(SSLManager::instance().securityFunctions())
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
Context::~Context()
|
||||
{
|
||||
if (_pCert)
|
||||
{
|
||||
CertFreeCertificateContext(_pCert);
|
||||
}
|
||||
if (_hCertStore)
|
||||
{
|
||||
CertCloseStore(_hCertStore, 0);
|
||||
}
|
||||
CertCloseStore(_hCollectionCertStore, 0);
|
||||
CertCloseStore(_hMemCertStore, 0);
|
||||
if (_hRootCertStore)
|
||||
{
|
||||
CertCloseStore(_hRootCertStore, 0);
|
||||
}
|
||||
if (_hCreds.dwLower != 0 && _hCreds.dwUpper != 0)
|
||||
{
|
||||
_securityFunctions.FreeCredentialsHandle(&_hCreds);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Context::init()
|
||||
{
|
||||
_hCreds.dwLower = 0;
|
||||
_hCreds.dwUpper = 0;
|
||||
|
||||
_hMemCertStore = CertOpenStore(
|
||||
CERT_STORE_PROV_MEMORY, // The memory provider type
|
||||
0, // The encoding type is not needed
|
||||
@@ -83,22 +126,229 @@ Context::Context(Usage usage,
|
||||
}
|
||||
|
||||
|
||||
Context::~Context()
|
||||
void Context::addTrustedCert(const Poco::Net::X509Certificate& cert)
|
||||
{
|
||||
CertCloseStore(_hCollectionCertStore, 0);
|
||||
CertCloseStore(_hMemCertStore, 0);
|
||||
if (_hRootCertStore)
|
||||
Poco::FastMutex::ScopedLock lock(_mutex);
|
||||
if (!CertAddCertificateContextToStore(_hMemCertStore, cert.system(), CERT_STORE_ADD_REPLACE_EXISTING, 0))
|
||||
throw CertificateException("Failed to add certificate to store", GetLastError());
|
||||
}
|
||||
|
||||
|
||||
Poco::Net::X509Certificate Context::certificate()
|
||||
{
|
||||
if (_pCert)
|
||||
return Poco::Net::X509Certificate(_pCert, true);
|
||||
|
||||
if (_certNameOrPath.empty())
|
||||
throw NoCertificateException("Certificate requested, but no certificate name or path provided");
|
||||
|
||||
if (_options & OPT_LOAD_CERT_FROM_FILE)
|
||||
{
|
||||
CertCloseStore(_hRootCertStore, 0);
|
||||
importCertificate();
|
||||
}
|
||||
else
|
||||
{
|
||||
loadCertificate();
|
||||
}
|
||||
|
||||
return Poco::Net::X509Certificate(_pCert, true);
|
||||
}
|
||||
|
||||
|
||||
void Context::loadCertificate()
|
||||
{
|
||||
std::wstring wcertStore;
|
||||
Poco::UnicodeConverter::convert(_certStoreName, wcertStore);
|
||||
if (!_hCertStore)
|
||||
{
|
||||
if (_options & OPT_USE_MACHINE_STORE)
|
||||
_hCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE, _certStoreName.c_str());
|
||||
else
|
||||
_hCertStore = CertOpenSystemStoreW(0, wcertStore.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 = (DWORD) _certNameOrPath.size();
|
||||
cert_rdn_attr.Value.pbData = (BYTE *) _certNameOrPath.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) throw NoCertificateException(Poco::format("Failed to find certificate %s in store %s", _certNameOrPath, _certStoreName));
|
||||
}
|
||||
|
||||
|
||||
void Context::importCertificate()
|
||||
{
|
||||
Poco::File certFile(_certNameOrPath);
|
||||
if (!certFile.exists()) throw Poco::FileNotFoundException(_certNameOrPath);
|
||||
Poco::File::FileSize size = certFile.getSize();
|
||||
if (size > 4096) throw Poco::DataFormatException("PKCS #12 certificate file too large", _certNameOrPath);
|
||||
Poco::Buffer<char> buffer(static_cast<std::size_t>(size));
|
||||
Poco::FileInputStream istr(_certNameOrPath);
|
||||
istr.read(buffer.begin(), buffer.size());
|
||||
if (istr.gcount() != size) throw Poco::IOException("error reading PKCS #12 certificate file");
|
||||
importCertificate(buffer.begin(), buffer.size());
|
||||
}
|
||||
|
||||
|
||||
void Context::importCertificate(const char* pBuffer, std::size_t size)
|
||||
{
|
||||
std::string password;
|
||||
SSLManager::instance().PrivateKeyPassphraseRequired.notify(&SSLManager::instance(), password);
|
||||
std::wstring wpassword;
|
||||
Poco::UnicodeConverter::toUTF16(password, wpassword);
|
||||
|
||||
// clear UTF-8 password
|
||||
std::fill(const_cast<char*>(password.data()), const_cast<char*>(password.data() + password.size()), 'X');
|
||||
|
||||
CRYPT_DATA_BLOB blob;
|
||||
blob.cbData = static_cast<DWORD>(size);
|
||||
blob.pbData = reinterpret_cast<BYTE*>(const_cast<char*>(pBuffer));
|
||||
|
||||
HCERTSTORE hTempStore = PFXImportCertStore(&blob, wpassword.data(), PKCS12_ALLOW_OVERWRITE_KEY | PKCS12_INCLUDE_EXTENDED_PROPERTIES);
|
||||
|
||||
// clear UTF-16 password
|
||||
std::fill(const_cast<wchar_t*>(wpassword.data()), const_cast<wchar_t*>(wpassword.data() + password.size()), L'X');
|
||||
|
||||
if (hTempStore)
|
||||
{
|
||||
PCCERT_CONTEXT pCert = 0;
|
||||
pCert = CertEnumCertificatesInStore(hTempStore, pCert);
|
||||
while (pCert)
|
||||
{
|
||||
PCCERT_CONTEXT pStoreCert = 0;
|
||||
BOOL res = CertAddCertificateContextToStore(_hMemCertStore, pCert, CERT_STORE_ADD_REPLACE_EXISTING, &pStoreCert);
|
||||
if (res)
|
||||
{
|
||||
if (!_pCert)
|
||||
{
|
||||
_pCert = pStoreCert;
|
||||
}
|
||||
else
|
||||
{
|
||||
CertFreeCertificateContext(pStoreCert);
|
||||
pStoreCert = 0;
|
||||
}
|
||||
}
|
||||
pCert = CertEnumCertificatesInStore(hTempStore, pCert);
|
||||
}
|
||||
CertCloseStore(hTempStore, 0);
|
||||
}
|
||||
else throw CertificateException("failed to import certificate", GetLastError());
|
||||
}
|
||||
|
||||
|
||||
CredHandle& Context::credentials()
|
||||
{
|
||||
Poco::FastMutex::ScopedLock lock(_mutex);
|
||||
|
||||
if (_hCreds.dwLower == 0 && _hCreds.dwUpper == 0)
|
||||
{
|
||||
acquireSchannelCredentials(_hCreds);
|
||||
}
|
||||
return _hCreds;
|
||||
}
|
||||
|
||||
|
||||
void Context::acquireSchannelCredentials(CredHandle& credHandle) const
|
||||
{
|
||||
SCHANNEL_CRED schannelCred;
|
||||
ZeroMemory(&schannelCred, sizeof(schannelCred));
|
||||
schannelCred.dwVersion = SCHANNEL_CRED_VERSION;
|
||||
|
||||
if (_pCert)
|
||||
{
|
||||
schannelCred.cCreds = 1; // how many cred are stored in &pCertContext
|
||||
schannelCred.paCred = &const_cast<PCCERT_CONTEXT>(_pCert);
|
||||
}
|
||||
|
||||
schannelCred.grbitEnabledProtocols = proto();
|
||||
|
||||
// Windows NT and Windows Me/98/95: revocation checking not supported via flags
|
||||
if (_options & Context::OPT_PERFORM_REVOCATION_CHECK)
|
||||
schannelCred.dwFlags |= SCH_CRED_REVOCATION_CHECK_CHAIN;
|
||||
else
|
||||
schannelCred.dwFlags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | SCH_CRED_IGNORE_REVOCATION_OFFLINE;
|
||||
|
||||
if (isForServerUse())
|
||||
{
|
||||
if (_mode == Context::VERIFY_STRICT)
|
||||
schannelCred.dwFlags |= SCH_CRED_NO_SYSTEM_MAPPER;
|
||||
|
||||
if (_mode == Context::VERIFY_NONE)
|
||||
schannelCred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_mode == Context::VERIFY_STRICT)
|
||||
schannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS;
|
||||
else
|
||||
schannelCred.dwFlags |= SCH_CRED_USE_DEFAULT_CREDS;
|
||||
|
||||
if (_mode == Context::VERIFY_NONE)
|
||||
schannelCred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION | SCH_CRED_NO_SERVERNAME_CHECK;
|
||||
}
|
||||
|
||||
#if defined(SCH_USE_STRONG_CRYPTO)
|
||||
if (_options & Context::OPT_USE_STRONG_CRYPTO)
|
||||
schannelCred.dwFlags |= SCH_USE_STRONG_CRYPTO;
|
||||
#endif
|
||||
|
||||
schannelCred.hRootStore = _hCollectionCertStore;
|
||||
|
||||
TimeStamp tsExpiry;
|
||||
tsExpiry.LowPart = tsExpiry.HighPart = 0;
|
||||
SECURITY_STATUS status = _securityFunctions.AcquireCredentialsHandleW(
|
||||
NULL,
|
||||
UNISP_NAME_W,
|
||||
isForServerUse() ? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND,
|
||||
NULL,
|
||||
&schannelCred,
|
||||
NULL,
|
||||
NULL,
|
||||
&credHandle,
|
||||
&tsExpiry);
|
||||
|
||||
if (status != SEC_E_OK)
|
||||
{
|
||||
throw SSLException("Failed to acquire Schannel credentials", Utility::formatError(status));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Context::addTrustedCert(const Poco::Net::X509Certificate& cert)
|
||||
DWORD Context::proto() const
|
||||
{
|
||||
Poco::FastMutex::ScopedLock lock(_mutex);
|
||||
CertAddCertificateContextToStore(_hMemCertStore, cert.system(), CERT_STORE_ADD_REPLACE_EXISTING, 0);
|
||||
switch (_usage)
|
||||
{
|
||||
case Context::CLIENT_USE:
|
||||
return SP_PROT_SSL3_CLIENT | SP_PROT_TLS1_CLIENT;
|
||||
case Context::SERVER_USE:
|
||||
return SP_PROT_SSL3_SERVER | SP_PROT_TLS1_SERVER;
|
||||
case Context::TLSV1_CLIENT_USE:
|
||||
return SP_PROT_TLS1_CLIENT;
|
||||
case Context::TLSV1_SERVER_USE:
|
||||
return SP_PROT_TLS1_SERVER;
|
||||
#if defined(SP_PROT_TLS1_1)
|
||||
case Context::TLSV1_1_CLIENT_USE:
|
||||
return SP_PROT_TLS1_1_CLIENT;
|
||||
case Context::TLSV1_1_SERVER_USE:
|
||||
return SP_PROT_TLS1_1_SERVER;
|
||||
#endif
|
||||
#if defined(SP_PROT_TLS1_2)
|
||||
case Context::TLSV1_2_CLIENT_USE:
|
||||
return SP_PROT_TLS1_2_CLIENT;
|
||||
case Context::TLSV1_2_SERVER_USE:
|
||||
return SP_PROT_TLS1_2_SERVER;
|
||||
#endif
|
||||
default:
|
||||
throw Poco::InvalidArgumentException("Unsupported SSL/TLS protocol version");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} } // namespace Poco::Net
|
||||
|
||||
Reference in New Issue
Block a user