(Pre-shared) client certificates are no longer maintained by hash only

This commit is contained in:
Simon Rozman 2016-06-16 00:29:56 +02:00
parent cda81dd696
commit d430b63829
5 changed files with 164 additions and 133 deletions

View File

@ -59,6 +59,9 @@ namespace eapserial
#pragma once #pragma once
#include "../../EAPBase/include/Credentials.h" #include "../../EAPBase/include/Credentials.h"
#include "../../EAPBase/include/EAPSerial.h"
#include <WinStd/Crypt.h>
#include <Windows.h> #include <Windows.h>
#include <vector> #include <vector>
@ -190,7 +193,7 @@ namespace eap
/// @} /// @}
public: public:
std::vector<unsigned char> m_cert_hash; ///< Client certificate hash (certificates are kept in Personal Certificate Storage) winstd::cert_context m_cert; ///< Client certificate
}; };
} }
@ -199,22 +202,30 @@ namespace eapserial
{ {
inline void pack(_Inout_ unsigned char *&cursor, _In_ const eap::credentials_tls &val) inline void pack(_Inout_ unsigned char *&cursor, _In_ const eap::credentials_tls &val)
{ {
pack(cursor, (const eap::credentials&)val); // Don't save m_identity. We rebuild it on every load.
pack(cursor, val.m_cert_hash ); //pack(cursor, (const eap::credentials&)val);
pack(cursor, val.m_cert );
} }
inline size_t get_pk_size(const eap::credentials_tls &val) inline size_t get_pk_size(const eap::credentials_tls &val)
{ {
return return
get_pk_size((const eap::credentials&)val) + // Don't save m_identity. We rebuild it on every load.
get_pk_size(val.m_cert_hash ); //get_pk_size((const eap::credentials&)val) +
get_pk_size(val.m_cert );
} }
inline void unpack(_Inout_ const unsigned char *&cursor, _Out_ eap::credentials_tls &val) inline void unpack(_Inout_ const unsigned char *&cursor, _Out_ eap::credentials_tls &val)
{ {
unpack(cursor, (eap::credentials&)val); // Don't load m_identity. We rebuild it on load.
unpack(cursor, val.m_cert_hash ); //unpack(cursor, (eap::credentials&)val);
unpack(cursor, val.m_cert );
if (val.m_cert) {
// Generate identity. TODO: Find which CERT_NAME_... constant returns valid identity (username@domain or DOMAIN\Username).
CertGetNameString(val.m_cert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, val.m_identity);
}
} }
} }

View File

@ -156,13 +156,23 @@ bool eap::config_tls::load(_In_ IXMLDOMNode *pConfigRoot, _Out_ EAP_ERROR **ppEa
com_obj<IXMLDOMNode> pXmlElCA; com_obj<IXMLDOMNode> pXmlElCA;
pXmlListCAs->get_item(j, &pXmlElCA); pXmlListCAs->get_item(j, &pXmlElCA);
bstr bstrFormat; bstr bstrFormat;
if (eapxml::get_element_value(pXmlElCA, bstr(L"eap-metadata:format"), &bstrFormat) == ERROR_SUCCESS) { if (eapxml::get_element_value(pXmlElCA, bstr(L"eap-metadata:format"), &bstrFormat) != ERROR_SUCCESS) {
if (CompareStringEx(LOCALE_NAME_INVARIANT, NORM_IGNORECASE, bstrFormat, bstrFormat.length(), L"PEM", -1, NULL, NULL, 0) == CSTR_EQUAL) { // <format> not specified.
vector<unsigned char> aData; continue;
if (eapxml::get_element_base64(pXmlElCA, bstr(L"eap-metadata:cert-data"), aData) == ERROR_SUCCESS)
add_trusted_ca(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, aData.data(), (DWORD)aData.size());
}
} }
if (CompareStringEx(LOCALE_NAME_INVARIANT, NORM_IGNORECASE, bstrFormat, bstrFormat.length(), L"PEM", -1, NULL, NULL, 0) != CSTR_EQUAL) {
// Certificate must be PEM encoded.
continue;
}
vector<unsigned char> aData;
if (eapxml::get_element_base64(pXmlElCA, bstr(L"eap-metadata:cert-data"), aData) != ERROR_SUCCESS) {
// Error reading <cert-data> element.
continue;
}
add_trusted_ca(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, aData.data(), (DWORD)aData.size());
} }
} }

View File

@ -34,14 +34,14 @@ eap::credentials_tls::credentials_tls(_In_ module &mod) : credentials(mod)
eap::credentials_tls::credentials_tls(_In_ const credentials_tls &other) : eap::credentials_tls::credentials_tls(_In_ const credentials_tls &other) :
m_cert_hash(other.m_cert_hash), m_cert(other.m_cert),
credentials(other) credentials(other)
{ {
} }
eap::credentials_tls::credentials_tls(_Inout_ credentials_tls &&other) : eap::credentials_tls::credentials_tls(_Inout_ credentials_tls &&other) :
m_cert_hash(std::move(other.m_cert_hash)), m_cert(std::move(other.m_cert)),
credentials(std::move(other)) credentials(std::move(other))
{ {
} }
@ -51,7 +51,7 @@ eap::credentials_tls& eap::credentials_tls::operator=(_In_ const credentials_tls
{ {
if (this != &other) { if (this != &other) {
(credentials&)*this = other; (credentials&)*this = other;
m_cert_hash = other.m_cert_hash; m_cert = other.m_cert;
} }
return *this; return *this;
@ -62,7 +62,7 @@ eap::credentials_tls& eap::credentials_tls::operator=(_Inout_ credentials_tls &&
{ {
if (this != &other) { if (this != &other) {
(credentials&)*this = std::move(other); (credentials&)*this = std::move(other);
m_cert_hash = std::move(other.m_cert_hash); m_cert = std::move(other.m_cert);
} }
return *this; return *this;
@ -78,13 +78,13 @@ eap::config* eap::credentials_tls::clone() const
void eap::credentials_tls::clear() void eap::credentials_tls::clear()
{ {
credentials::clear(); credentials::clear();
m_cert_hash.clear(); m_cert.free();
} }
bool eap::credentials_tls::empty() const bool eap::credentials_tls::empty() const
{ {
return credentials::empty() && m_cert_hash.empty(); return credentials::empty() && !m_cert;
} }
@ -92,13 +92,35 @@ bool eap::credentials_tls::save(_In_ IXMLDOMDocument *pDoc, _In_ IXMLDOMNode *pC
{ {
const bstr bstrNamespace(L"urn:ietf:params:xml:ns:yang:ietf-eap-metadata"); const bstr bstrNamespace(L"urn:ietf:params:xml:ns:yang:ietf-eap-metadata");
DWORD dwResult; DWORD dwResult;
HRESULT hr;
if (!credentials::save(pDoc, pConfigRoot, ppEapError)) // Don't save m_identity. We rebuild it on every load.
//if (!credentials::save(pDoc, pConfigRoot, ppEapError))
// return false;
// <ClientCertificate>
com_obj<IXMLDOMElement> pXmlElClientCertificate;
if ((dwResult = eapxml::create_element(pDoc, bstr(L"ClientCertificate"), bstrNamespace, &pXmlElClientCertificate))) {
*ppEapError = m_module.make_error(dwResult, 0, NULL, NULL, NULL, _T(__FUNCTION__) _T(" Error creating <ClientCertificate> element."), NULL);
return false; return false;
}
// <CertHash> if (m_cert) {
if ((dwResult = eapxml::put_element_hex(pDoc, pConfigRoot, bstr(L"CertHash"), bstrNamespace, m_cert_hash.data(), m_cert_hash.size())) != ERROR_SUCCESS) { // <ClientCertificate>/<format>
*ppEapError = m_module.make_error(dwResult, 0, NULL, NULL, NULL, _T(__FUNCTION__) _T(" Error creating <CertHash> element."), NULL); if ((dwResult = eapxml::put_element_value(pDoc, pXmlElClientCertificate, bstr(L"format"), bstrNamespace, bstr(L"PEM"))) != ERROR_SUCCESS) {
*ppEapError = m_module.make_error(dwResult, 0, NULL, NULL, NULL, _T(__FUNCTION__) _T(" Error creating <format> element."), NULL);
return false;
}
// <ClientCertificate>/<cert-data>
if ((dwResult = eapxml::put_element_base64(pDoc, pXmlElClientCertificate, bstr(L"cert-data"), bstrNamespace, m_cert->pbCertEncoded, m_cert->cbCertEncoded)) != ERROR_SUCCESS) {
*ppEapError = m_module.make_error(dwResult, 0, NULL, NULL, NULL, _T(__FUNCTION__) _T(" Error creating <cert-data> element."), NULL);
return false;
}
}
if (FAILED(hr = pConfigRoot->appendChild(pXmlElClientCertificate, NULL))) {
*ppEapError = m_module.make_error(HRESULT_CODE(hr), 0, NULL, NULL, NULL, _T(__FUNCTION__) _T(" Error appending <ClientCertificate> element."), NULL);
return false; return false;
} }
@ -111,15 +133,35 @@ bool eap::credentials_tls::load(_In_ IXMLDOMNode *pConfigRoot, _Out_ EAP_ERROR *
assert(pConfigRoot); assert(pConfigRoot);
DWORD dwResult; DWORD dwResult;
if (!credentials::load(pConfigRoot, ppEapError)) // Don't load m_identity. We rebuild it on load.
return false; //if (!credentials::load(pConfigRoot, ppEapError))
// return false;
// <CertHash> m_identity.clear();
if ((dwResult = eapxml::get_element_hex(pConfigRoot, bstr(L"eap-metadata:CertHash"), m_cert_hash)) != ERROR_SUCCESS) { m_cert.free();
*ppEapError = m_module.make_error(dwResult, 0, NULL, NULL, NULL, _T(__FUNCTION__) _T(" Error reading <CertHash> element."), NULL);
// <ClientCertificate>
com_obj<IXMLDOMElement> pXmlElClientCertificate;
if ((dwResult = eapxml::select_element(pConfigRoot, bstr(L"eap-metadata:ClientCertificate"), &pXmlElClientCertificate)) != ERROR_SUCCESS) {
*ppEapError = m_module.make_error(dwResult, 0, NULL, NULL, NULL, _T(__FUNCTION__) _T(" Error reading <ClientCertificate> element."), NULL);
return false; return false;
} }
// <ClientCertificate>/<format>
bstr bstrFormat;
if ((dwResult = eapxml::get_element_value(pXmlElClientCertificate, bstr(L"eap-metadata:format"), &bstrFormat)) == ERROR_SUCCESS) {
if (CompareStringEx(LOCALE_NAME_INVARIANT, NORM_IGNORECASE, bstrFormat, bstrFormat.length(), L"PEM", -1, NULL, NULL, 0) == CSTR_EQUAL) {
// <ClientCertificate>/<cert-data>
vector<unsigned char> aData;
if ((dwResult = eapxml::get_element_base64(pXmlElClientCertificate, bstr(L"eap-metadata:cert-data"), aData)) == ERROR_SUCCESS) {
if (m_cert.create(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, aData.data(), (DWORD)aData.size())) {
// Generate identity. TODO: Find which CERT_NAME_... constant returns valid identity (username@domain or DOMAIN\Username).
CertGetNameString(m_cert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, m_identity);
}
}
}
}
return true; return true;
} }
@ -128,11 +170,36 @@ bool eap::credentials_tls::store(_In_ LPCTSTR pszTargetName, _Out_ EAP_ERROR **p
{ {
assert(pszTargetName); assert(pszTargetName);
assert(ppEapError); assert(ppEapError);
string cert_enc;
// Prepare cryptographics provider.
crypt_prov cp;
if (!cp.create(NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
*ppEapError = m_module.make_error(GetLastError(), 0, NULL, NULL, NULL, _T(__FUNCTION__) _T(" CryptAcquireContext failed."), NULL);
return false;
}
// Encrypt certificate.
vector<unsigned char> cert;
if (!m_module.encrypt_md5(cp, m_cert->pbCertEncoded, m_cert->cbCertEncoded, cert, ppEapError))
return false;
// Convert encrypted certificate to Base64, since CredProtectA() fail for binary strings.
string cert_base64;
base64_enc enc;
enc.encode(cert_base64, cert.data(), cert.size());
// Encrypt the certificate using user's key.
CRED_PROTECTION_TYPE cpt;
if (!CredProtectA(TRUE, cert_base64.c_str(), (DWORD)cert_base64.length(), cert_enc, &cpt)) {
*ppEapError = m_module.make_error(GetLastError(), 0, NULL, NULL, NULL, _T(__FUNCTION__) _T(" CredProtect failed."), NULL);
return false;
}
tstring target(target_name(pszTargetName)); tstring target(target_name(pszTargetName));
// Write credentials. // Write credentials.
assert(m_cert_hash.size() < CRED_MAX_CREDENTIAL_BLOB_SIZE); assert(cert_enc.size() < CRED_MAX_CREDENTIAL_BLOB_SIZE);
assert(m_identity.length() < CRED_MAX_USERNAME_LENGTH ); assert(m_identity.length() < CRED_MAX_USERNAME_LENGTH );
CREDENTIAL cred = { CREDENTIAL cred = {
0, // Flags 0, // Flags
@ -140,8 +207,8 @@ bool eap::credentials_tls::store(_In_ LPCTSTR pszTargetName, _Out_ EAP_ERROR **p
(LPTSTR)target.c_str(), // TargetName (LPTSTR)target.c_str(), // TargetName
_T(""), // Comment _T(""), // Comment
{ 0, 0 }, // LastWritten { 0, 0 }, // LastWritten
(DWORD)m_cert_hash.size(), // CredentialBlobSize (DWORD)cert_enc.size(), // CredentialBlobSize
(LPBYTE)m_cert_hash.data(), // CredentialBlob (LPBYTE)cert_enc.data(), // CredentialBlob
CRED_PERSIST_ENTERPRISE, // Persist CRED_PERSIST_ENTERPRISE, // Persist
0, // AttributeCount 0, // AttributeCount
NULL, // Attributes NULL, // Attributes
@ -159,7 +226,7 @@ bool eap::credentials_tls::store(_In_ LPCTSTR pszTargetName, _Out_ EAP_ERROR **p
bool eap::credentials_tls::retrieve(_In_ LPCTSTR pszTargetName, _Out_ EAP_ERROR **ppEapError) bool eap::credentials_tls::retrieve(_In_ LPCTSTR pszTargetName, _Out_ EAP_ERROR **ppEapError)
{ {
assert(pszTargetName && _tcslen(pszTargetName) < CRED_MAX_GENERIC_TARGET_NAME_LENGTH); assert(pszTargetName);
// Read credentials. // Read credentials.
unique_ptr<CREDENTIAL, CredFree_delete<CREDENTIAL> > cred; unique_ptr<CREDENTIAL, CredFree_delete<CREDENTIAL> > cred;
@ -168,12 +235,38 @@ bool eap::credentials_tls::retrieve(_In_ LPCTSTR pszTargetName, _Out_ EAP_ERROR
return false; return false;
} }
if (cred->UserName) // Decrypt the certificate using user's key.
m_identity = cred->UserName; string cert_base64;
else if (!CredUnprotectA(TRUE, (LPCSTR)(cred->CredentialBlob), cred->CredentialBlobSize, cert_base64)) {
m_identity.clear(); *ppEapError = m_module.make_error(GetLastError(), 0, NULL, NULL, NULL, _T(__FUNCTION__) _T(" CredUnprotect failed."), NULL);
return false;
}
m_cert_hash.assign(cred->CredentialBlob, cred->CredentialBlob + cred->CredentialBlobSize); // Convert Base64 to binary encrypted certificate, since CredProtectA() fail for binary strings.
vector<unsigned char> cert;
base64_dec dec;
bool is_last;
dec.decode(cert, is_last, cert_base64.c_str(), cert_base64.length());
// Prepare cryptographics provider.
crypt_prov cp;
if (!cp.create(NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
*ppEapError = m_module.make_error(GetLastError(), 0, NULL, NULL, NULL, _T(__FUNCTION__) _T(" CryptAcquireContext failed."), NULL);
return false;
}
// Decrypt certificate.
vector<unsigned char, sanitizing_allocator<unsigned char> > _cert;
if (!m_module.decrypt_md5(cp, cert.data(), cert.size(), _cert, ppEapError))
return false;
if (!m_cert.create(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, _cert.data(), (DWORD)_cert.size())) {
*ppEapError = m_module.make_error(GetLastError(), 0, NULL, NULL, NULL, _T(__FUNCTION__) _T(" Error loading certificate."), NULL);
return false;
}
// Generate identity. TODO: Find which CERT_NAME_... constant returns valid identity (username@domain or DOMAIN\Username).
CertGetNameString(m_cert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, m_identity);
return true; return true;
} }

View File

@ -33,11 +33,6 @@
/// ///
class wxCertificateClientData; class wxCertificateClientData;
///
/// Helper class for auto-destroyable certificates used in wxWidget's item containers
///
class wxCertificateSelectionClientData;
/// ///
/// Validator for host name /// Validator for host name
/// ///
@ -116,45 +111,6 @@ public:
}; };
class wxCertificateSelectionClientData : public wxClientData
{
public:
///
/// Default constructor
///
wxCertificateSelectionClientData();
///
/// Constructs client data object
///
wxCertificateSelectionClientData(const wchar_t *identity, unsigned char *hash, size_t hash_size);
///
/// Constructs client data object with copy
///
wxCertificateSelectionClientData(const std::wstring &identity, const std::vector<unsigned char> &hash);
///
/// Constructs client data object with move
///
wxCertificateSelectionClientData(std::wstring &&identity, std::vector<unsigned char> &&hash);
///
/// Constructs client data object with copy
///
wxCertificateSelectionClientData(const wxCertificateSelectionClientData &other);
///
/// Constructs client data object with move
///
wxCertificateSelectionClientData(wxCertificateSelectionClientData &&other);
public:
std::wstring m_identity; ///< Client identity
std::vector<unsigned char> m_hash; ///< Client certificate hash (certificates are kept in Personal Certificate Storage)
};
class wxHostNameValidator : public wxValidator class wxHostNameValidator : public wxValidator
{ {
wxDECLARE_DYNAMIC_CLASS(wxHostNameValidator); wxDECLARE_DYNAMIC_CLASS(wxHostNameValidator);

View File

@ -76,50 +76,6 @@ wxCertificateClientData::~wxCertificateClientData()
} }
//////////////////////////////////////////////////////////////////////
// wxCertificateSelectionClientData
//////////////////////////////////////////////////////////////////////
wxCertificateSelectionClientData::wxCertificateSelectionClientData()
{
}
wxCertificateSelectionClientData::wxCertificateSelectionClientData(const wchar_t *identity, unsigned char *hash, size_t hash_size) :
m_identity(identity),
m_hash(hash, hash + hash_size)
{
}
wxCertificateSelectionClientData::wxCertificateSelectionClientData(const std::wstring &identity, const std::vector<unsigned char> &hash) :
m_identity(identity),
m_hash(hash)
{
}
wxCertificateSelectionClientData::wxCertificateSelectionClientData(std::wstring &&identity, std::vector<unsigned char> &&hash) :
m_identity(std::move(identity)),
m_hash(std::move(hash))
{
}
wxCertificateSelectionClientData::wxCertificateSelectionClientData(const wxCertificateSelectionClientData &other) :
m_identity(other.m_identity),
m_hash(other.m_hash)
{
}
wxCertificateSelectionClientData::wxCertificateSelectionClientData(wxCertificateSelectionClientData &&other) :
m_identity(std::move(other.m_identity)),
m_hash(std::move(other.m_hash))
{
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// wxHostNameValidator // wxHostNameValidator
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -417,13 +373,16 @@ bool wxEAPTLSCredentialsPanel::TransferDataToWindow()
} }
// Prepare certificate information. // Prepare certificate information.
std::unique_ptr<wxCertificateSelectionClientData> data(new wxCertificateSelectionClientData); std::unique_ptr<wxCertificateClientData> data(new wxCertificateClientData(CertDuplicateCertificateContext(cert)));
eap::get_cert_title(cert, data->m_identity);
// Add to list. // Add to list.
CertGetCertificateContextProperty(cert, CERT_HASH_PROP_ID, data->m_hash); bool is_selected =
bool is_selected = data->m_hash == m_cred.m_cert_hash; m_cred.m_cert &&
int i = m_cert_select_val->Append(data->m_identity, data.release()); m_cred.m_cert->cbCertEncoded == data->m_cert->cbCertEncoded &&
memcmp(m_cred.m_cert->pbCertEncoded, data->m_cert->pbCertEncoded, m_cred.m_cert->cbCertEncoded) == 0;
winstd::tstring name;
eap::get_cert_title(cert, name);
int i = m_cert_select_val->Append(name, data.release());
if (is_selected) { if (is_selected) {
m_cert_select_val->SetSelection(i); m_cert_select_val->SetSelection(i);
is_found = true; is_found = true;
@ -450,10 +409,12 @@ bool wxEAPTLSCredentialsPanel::TransferDataFromWindow()
if (m_cert_none->GetValue()) if (m_cert_none->GetValue())
m_cred.clear(); m_cred.clear();
else { else {
const wxCertificateSelectionClientData *data = dynamic_cast<const wxCertificateSelectionClientData*>(m_cert_select_val->GetClientObject(m_cert_select_val->GetSelection())); const wxCertificateClientData *data = dynamic_cast<const wxCertificateClientData*>(m_cert_select_val->GetClientObject(m_cert_select_val->GetSelection()));
if (data) { if (data) {
m_cred.m_identity = data->m_identity; m_cred.m_cert.attach_duplicated(data->m_cert);
m_cred.m_cert_hash = data->m_hash;
// Generate identity. TODO: Find which CERT_NAME_... constant returns valid identity (username@domain or DOMAIN\Username).
CertGetNameString(m_cred.m_cert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, m_cred.m_identity);
} else } else
m_cred.clear(); m_cred.clear();
} }