From eb36109d7f778a42abf2ea5efb24a2d6a9384a4c Mon Sep 17 00:00:00 2001 From: Hussein Ismail Date: Tue, 30 Jan 2018 16:10:00 +0200 Subject: [PATCH 1/2] Find certificate from Windows store using its thumbprint --- NetSSL_Win/include/Poco/Net/Context.h | 8 +-- NetSSL_Win/include/Poco/Net/SSLManager.h | 6 +++ NetSSL_Win/src/Context.cpp | 69 +++++++++++++++++------- NetSSL_Win/src/SSLManager.cpp | 16 ++++-- 4 files changed, 74 insertions(+), 25 deletions(-) diff --git a/NetSSL_Win/include/Poco/Net/Context.h b/NetSSL_Win/include/Poco/Net/Context.h index 090a0cf7ce..1ca285976f 100644 --- a/NetSSL_Win/include/Poco/Net/Context.h +++ b/NetSSL_Win/include/Poco/Net/Context.h @@ -111,11 +111,13 @@ class NetSSL_Win_API Context: public Poco::RefCountedObject OPT_LOAD_CERT_FROM_FILE = 0x10, /// Load certificate and private key from a PKCS #12 (.pfx) file, /// and not from the certificate store. + OPT_USE_CERT_HASH = 0x20, + /// Find the certificate using thumbprint. OPT_DEFAULTS = OPT_PERFORM_REVOCATION_CHECK | OPT_TRUST_ROOTS_WIN_CERT_STORE | OPT_USE_STRONG_CRYPTO }; Context(Usage usage, - const std::string& certificateNameOrPath, + const std::string& certificateInfoOrPath, VerificationMode verMode = VERIFY_RELAXED, int options = OPT_DEFAULTS, const std::string& certificateStoreName = CERT_STORE_MY); @@ -123,7 +125,7 @@ class NetSSL_Win_API Context: public Poco::RefCountedObject /// /// * usage specifies whether the context is used by a client or server, /// as well as which protocol to use. - /// * certificateNameOrPath specifies either the subject name of the certificate to use, + /// * certificateInfoOrPath specifies either the subject name or thumbprint of the certificate to use, /// or the path of a PKCS #12 file containing the certificate and corresponding private key. /// If a subject name is specified, the certificate must be located in the certificate /// store specified by certificateStoreName. If a path is given, the OPT_LOAD_CERT_FROM_FILE @@ -208,7 +210,7 @@ class NetSSL_Win_API Context: public Poco::RefCountedObject Context::VerificationMode _mode; int _options; bool _extendedCertificateVerification; - std::string _certNameOrPath; + std::string _certInfoOrPath; std::string _certStoreName; HCERTSTORE _hMemCertStore; HCERTSTORE _hCollectionCertStore; diff --git a/NetSSL_Win/include/Poco/Net/SSLManager.h b/NetSSL_Win/include/Poco/Net/SSLManager.h index e2e063ff7b..fa6e18f85e 100644 --- a/NetSSL_Win/include/Poco/Net/SSLManager.h +++ b/NetSSL_Win/include/Poco/Net/SSLManager.h @@ -71,6 +71,8 @@ class NetSSL_Win_API SSLManager /// /// /// cert Id + /// cert thumbprint + /// path of a certificate /// MY /// none|relaxed|strict /// true|false @@ -101,6 +103,8 @@ class NetSSL_Win_API SSLManager /// /// - certificateName (string): The subject name of the certificate to use. The certificate must /// be available in the Windows user or machine certificate store. + /// - certificateHash (string): The thumbprint of the certificate to use. Alternative for certificateName. + /// The certificate must be available in the Windows user or machine certificate store. /// - certificatePath (string): The path of a certificate and private key file in PKCS #12 format. /// - certificateStore (string): The certificate store location to use. /// Valid values are "MY", "Root", "Trust" or "CA". Defaults to "MY". @@ -267,6 +271,8 @@ class NetSSL_Win_API SSLManager static const std::string CFG_CERT_NAME; static const std::string VAL_CERT_NAME; + static const std::string CFG_CERT_HASH; + static const std::string VAL_CERT_HASH; static const std::string CFG_CERT_PATH; static const std::string VAL_CERT_PATH; static const std::string CFG_CERT_STORE; diff --git a/NetSSL_Win/src/Context.cpp b/NetSSL_Win/src/Context.cpp index afe1a729a7..60f5901e07 100644 --- a/NetSSL_Win/src/Context.cpp +++ b/NetSSL_Win/src/Context.cpp @@ -23,8 +23,11 @@ #include "Poco/MemoryStream.h" #include "Poco/Base64Decoder.h" #include "Poco/Buffer.h" +#include "Poco/HexBinaryDecoder.h" +#include #include #include +#include namespace Poco { @@ -39,7 +42,7 @@ const std::string Context::CERT_STORE_USERDS("USERDS"); Context::Context(Usage usage, - const std::string& certNameOrPath, + const std::string& certificateInfoOrPath, VerificationMode verMode, int options, const std::string& certStore): @@ -47,7 +50,7 @@ Context::Context(Usage usage, _mode(verMode), _options(options), _extendedCertificateVerification(true), - _certNameOrPath(certNameOrPath), + _certInfoOrPath(certificateInfoOrPath), _certStoreName(certStore), _hMemCertStore(0), _hCollectionCertStore(0), @@ -144,7 +147,7 @@ Poco::Net::X509Certificate Context::certificate() if (_pCert) return Poco::Net::X509Certificate(_pCert, true); - if (_certNameOrPath.empty()) + if (_certInfoOrPath.empty()) throw NoCertificateException("Certificate requested, but no certificate name or path provided"); if (_options & OPT_LOAD_CERT_FROM_FILE) @@ -167,35 +170,65 @@ void Context::loadCertificate() if (!_hCertStore) { if (_options & OPT_USE_MACHINE_STORE) - _hCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE, _certStoreName.c_str()); + _hCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_OPEN_EXISTING_FLAG, wcertStore.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(); + // Find the certificate either using name or hash. + if(_options & OPT_USE_CERT_HASH) + { + // Sanity check for the hash value. + if(_certInfoOrPath.size() % 2) throw CertificateException(Poco::format("Invalid certificate hash %s", _certInfoOrPath)); + + // Convert hex to binary. + BYTE buffer[256] = {}; + int bufferSize = 0; + int byte = 0; + std::string szHex; + for(int counter = 0; counter < _certInfoOrPath.size() / 2; counter++) + { + szHex = _certInfoOrPath.substr(2 * counter, 2); + if( sscanf(szHex.c_str(), "%02x", OUT &byte ) != 1) + throw CertificateException(Poco::format("Invalid certificate hash %s", _certInfoOrPath)); + buffer[counter] = (BYTE) byte; + bufferSize += 1; + } - CERT_RDN cert_rdn; - cert_rdn.cRDNAttr = 1; - cert_rdn.rgRDNAttr = &cert_rdn_attr; + CRYPT_HASH_BLOB chBlob; + chBlob.cbData = bufferSize; + chBlob.pbData = buffer; - _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)); + _pCert = CertFindCertificateInStore(_hCertStore, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, CERT_FIND_HASH, &chBlob, NULL); + if (!_pCert) throw NoCertificateException(Poco::format("Failed to find certificate %s in store %s", _certInfoOrPath, _certStoreName)); + } + else + { + 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) _certInfoOrPath.size(); + cert_rdn_attr.Value.pbData = (BYTE *) _certInfoOrPath.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", _certInfoOrPath, _certStoreName)); + } } void Context::importCertificate() { - Poco::File certFile(_certNameOrPath); - if (!certFile.exists()) throw Poco::FileNotFoundException(_certNameOrPath); + Poco::File certFile(_certInfoOrPath); + if (!certFile.exists()) throw Poco::FileNotFoundException(_certInfoOrPath); Poco::File::FileSize size = certFile.getSize(); - if (size > 4096) throw Poco::DataFormatException("PKCS #12 certificate file too large", _certNameOrPath); + if (size > 4096) throw Poco::DataFormatException("PKCS #12 certificate file too large", _certInfoOrPath); Poco::Buffer buffer(static_cast(size)); - Poco::FileInputStream istr(_certNameOrPath); + Poco::FileInputStream istr(_certInfoOrPath); istr.read(buffer.begin(), buffer.size()); if (istr.gcount() != size) throw Poco::IOException("error reading PKCS #12 certificate file"); importCertificate(buffer.begin(), buffer.size()); diff --git a/NetSSL_Win/src/SSLManager.cpp b/NetSSL_Win/src/SSLManager.cpp index 1e6f0fa227..2f6c1efeb5 100644 --- a/NetSSL_Win/src/SSLManager.cpp +++ b/NetSSL_Win/src/SSLManager.cpp @@ -29,6 +29,8 @@ namespace Net { const std::string SSLManager::CFG_CERT_NAME("certificateName"); const std::string SSLManager::VAL_CERT_NAME(""); +const std::string SSLManager::CFG_CERT_HASH("certificateHash"); +const std::string SSLManager::VAL_CERT_HASH(""); const std::string SSLManager::CFG_CERT_PATH("certificatePath"); const std::string SSLManager::VAL_CERT_PATH(""); const std::string SSLManager::CFG_CERT_STORE("certificateStore"); @@ -188,7 +190,8 @@ void SSLManager::initDefaultContext(bool server) const std::string prefix = server ? CFG_SERVER_PREFIX : CFG_CLIENT_PREFIX; Poco::Util::AbstractConfiguration& config = appConfig(); - std::string certName = config.getString(prefix + CFG_CERT_NAME, VAL_CERT_NAME); + std::string certInfo = config.getString(prefix + CFG_CERT_NAME, VAL_CERT_NAME); + std::string certHash = config.getString(prefix + CFG_CERT_HASH, VAL_CERT_HASH); std::string certPath = config.getString(prefix + CFG_CERT_PATH, VAL_CERT_PATH); std::string certStore = config.getString(prefix + CFG_CERT_STORE, VAL_CERT_STORE); @@ -217,7 +220,12 @@ void SSLManager::initDefaultContext(bool server) if (!certPath.empty()) { options |= Context::OPT_LOAD_CERT_FROM_FILE; - certName = certPath; + certInfo = certPath; + } + if (certInfo.empty() && !certHash.empty()) + { + options |= Context::OPT_USE_CERT_HASH; + certInfo = certHash; } Context::Usage usage; @@ -231,7 +239,7 @@ void SSLManager::initDefaultContext(bool server) usage = Context::TLSV1_SERVER_USE; else usage = Context::SERVER_USE; - _ptrDefaultServerContext = new Context(usage, certName, verMode, options, certStore); + _ptrDefaultServerContext = new Context(usage, certInfo, verMode, options, certStore); } else { @@ -243,7 +251,7 @@ void SSLManager::initDefaultContext(bool server) usage = Context::TLSV1_CLIENT_USE; else usage = Context::CLIENT_USE; - _ptrDefaultClientContext = new Context(usage, certName, verMode, options, certStore); + _ptrDefaultClientContext = new Context(usage, certInfo, verMode, options, certStore); } } From 871937bd9557369d74717351271202793a1f5ef3 Mon Sep 17 00:00:00 2001 From: Hussein Ismail Date: Mon, 5 Feb 2018 12:27:12 +0200 Subject: [PATCH 2/2] Address review comments --- NetSSL_Win/src/Context.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/NetSSL_Win/src/Context.cpp b/NetSSL_Win/src/Context.cpp index 60f5901e07..59120c3677 100644 --- a/NetSSL_Win/src/Context.cpp +++ b/NetSSL_Win/src/Context.cpp @@ -23,7 +23,6 @@ #include "Poco/MemoryStream.h" #include "Poco/Base64Decoder.h" #include "Poco/Buffer.h" -#include "Poco/HexBinaryDecoder.h" #include #include #include @@ -180,7 +179,7 @@ void Context::loadCertificate() if(_options & OPT_USE_CERT_HASH) { // Sanity check for the hash value. - if(_certInfoOrPath.size() % 2) throw CertificateException(Poco::format("Invalid certificate hash %s", _certInfoOrPath)); + if(_certInfoOrPath.size() < 40 || _certInfoOrPath.size() % 2) throw CertificateException(Poco::format("Invalid certificate hash %s", _certInfoOrPath)); // Convert hex to binary. BYTE buffer[256] = {};