diff options
Diffstat (limited to 'cpp/src/IceSSL/SChannelCertificateI.cpp')
-rw-r--r-- | cpp/src/IceSSL/SChannelCertificateI.cpp | 569 |
1 files changed, 569 insertions, 0 deletions
diff --git a/cpp/src/IceSSL/SChannelCertificateI.cpp b/cpp/src/IceSSL/SChannelCertificateI.cpp new file mode 100644 index 00000000000..e9aff0aa4b0 --- /dev/null +++ b/cpp/src/IceSSL/SChannelCertificateI.cpp @@ -0,0 +1,569 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +#include <IceSSL/Plugin.h> +#include <IceSSL/SChannel.h> +#include <IceSSL/CertificateI.h> +#include <IceSSL/Util.h> +#include <Ice/StringUtil.h> +#include <Ice/StringConverter.h> + +#include <wincrypt.h> + +#include <string> +#include <vector> + +using namespace std; +using namespace Ice; +using namespace IceSSL; + +namespace +{ + +class SCHannelX509ExtensionI : public X509Extension +{ + +public: + + SCHannelX509ExtensionI(CERT_EXTENSION , const string&, const SChannel::CertificatePtr&); + virtual bool isCritical() const; + virtual string getOID() const; + virtual vector<Ice::Byte> getData() const; + +private: + + CERT_EXTENSION _extension; + string _oid; + // + // We want to keep the certificate that contains the extension alive + // for the lifetime of the extension. + // + SChannel::CertificatePtr _cert; +}; + +class SChannelCertificateI : public SChannel::Certificate, + public CertificateI, + public IceUtil::Mutex +{ +public: + + SChannelCertificateI(CERT_SIGNED_CONTENT_INFO*); + ~SChannelCertificateI(); + + virtual bool operator==(const IceSSL::Certificate&) const; + + virtual vector<Ice::Byte> getAuthorityKeyIdentifier() const; + virtual vector<Ice::Byte> getSubjectKeyIdentifier() const; + virtual bool verify(const CertificatePtr&) const; + virtual string encode() const; + +#ifdef ICE_CPP11_MAPPING + virtual chrono::system_clock::time_point getNotAfter() const; + virtual chrono::system_clock::time_point getNotBefore() const; +#else + virtual IceUtil::Time getNotAfter() const; + virtual IceUtil::Time getNotBefore() const; +#endif + virtual string getSerialNumber() const; + virtual DistinguishedName getIssuerDN() const; + virtual vector<pair<int, string> > getIssuerAlternativeNames() const; + virtual DistinguishedName getSubjectDN() const; + virtual vector<pair<int, string> > getSubjectAlternativeNames() const; + virtual int getVersion() const; + virtual CERT_SIGNED_CONTENT_INFO* getCert() const; + +protected: + + virtual void loadX509Extensions() const; + +private: + + CERT_SIGNED_CONTENT_INFO* _cert; + CERT_INFO* _certInfo; +}; + +const Ice::Long TICKS_PER_MSECOND = 10000LL; +const Ice::Long MSECS_TO_EPOCH = 11644473600000LL; + +void +loadCertificate(PCERT_SIGNED_CONTENT_INFO* cert, const char* buffer, DWORD length) +{ + DWORD outLength = length; + vector<BYTE> outBuffer; + outBuffer.resize(outLength); + + if(!CryptStringToBinary(buffer, length, CRYPT_STRING_BASE64HEADER, &outBuffer[0], &outLength, 0, 0)) + { + // + // Base64 data should always be bigger than binary + // + assert(GetLastError() != ERROR_MORE_DATA); + throw CertificateEncodingException(__FILE__, __LINE__, IceUtilInternal::lastErrorToString()); + } + + DWORD decodedLeng = 0; + if(!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_CERT, &outBuffer[0], outLength, CRYPT_DECODE_ALLOC_FLAG, 0, + cert, &decodedLeng)) + { + throw CertificateEncodingException(__FILE__, __LINE__, IceUtilInternal::lastErrorToString()); + } +} + +void +loadCertificate(PCERT_SIGNED_CONTENT_INFO* cert, const string& file) +{ + vector<char> buffer; + readFile(file, buffer); + if(buffer.empty()) + { + throw CertificateReadException(__FILE__, __LINE__, "certificate file " + file + " is empty"); + } + loadCertificate(cert, &buffer[0], static_cast<DWORD>(buffer.size())); +} + +# ifdef ICE_CPP11_MAPPING +chrono::system_clock::time_point +# else +IceUtil::Time +# endif +filetimeToTime(FILETIME ftime) +{ + Ice::Long value = 0; + DWORD* dest = reinterpret_cast<DWORD*>(&value); + *dest++ = ftime.dwLowDateTime; + *dest = ftime.dwHighDateTime; + + IceUtil::Time time = IceUtil::Time::milliSeconds((value / TICKS_PER_MSECOND) - MSECS_TO_EPOCH); + +# ifdef ICE_CPP11_MAPPING + return chrono::system_clock::time_point(chrono::microseconds(time.toMicroSeconds())); +# else + return time; +# endif +} + +string +certNameToString(CERT_NAME_BLOB* name) +{ + assert(name); + DWORD length = 0; + if(!(length = CertNameToStr(X509_ASN_ENCODING, name, CERT_OID_NAME_STR|CERT_NAME_STR_REVERSE_FLAG, 0, 0))) + { + throw CertificateEncodingException(__FILE__, __LINE__, IceUtilInternal::lastErrorToString()); + } + + vector<char> buffer(length); + if(!CertNameToStr(X509_ASN_ENCODING, name, CERT_OID_NAME_STR|CERT_NAME_STR_REVERSE_FLAG, &buffer[0], length)) + { + throw CertificateEncodingException(__FILE__, __LINE__, IceUtilInternal::lastErrorToString()); + } + + string s(&buffer[0]); + for(int i = 0; i < certificateOIDSSize; ++i) + { + const CertificateOID* certificateOID = &certificateOIDS[i]; + assert(certificateOID); + const string name = string(certificateOID->name) + "="; + const string alias = string(certificateOID->alias) + "="; + size_t pos = 0; + while((pos = s.find(name, pos)) != string::npos) + { + s.replace(pos, name.size(), alias); + } + } + return s; +} + +vector<pair<int, string> > +certificateAltNames(CERT_INFO* certInfo, LPCSTR altNameOID) +{ + vector<pair<int, string> > altNames; + + PCERT_EXTENSION extension = CertFindExtension(altNameOID, certInfo->cExtension, certInfo->rgExtension); + if(extension) + { + CERT_ALT_NAME_INFO* altName; + DWORD length = 0; + if(!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_ALTERNATE_NAME, extension->Value.pbData, + extension->Value.cbData, CRYPT_DECODE_ALLOC_FLAG, 0, &altName, &length)) + { + throw CertificateEncodingException(__FILE__, __LINE__, IceUtilInternal::lastErrorToString()); + } + + for(DWORD i = 0; i < altName->cAltEntry; ++i) + { + CERT_ALT_NAME_ENTRY* entry = &altName->rgAltEntry[i]; + + switch(entry->dwAltNameChoice) + { + case CERT_ALT_NAME_RFC822_NAME: + { + altNames.push_back(make_pair(AltNameEmail, Ice::wstringToString(entry->pwszRfc822Name))); + break; + } + case CERT_ALT_NAME_DNS_NAME: + { + altNames.push_back(make_pair(AltNameDNS, Ice::wstringToString(entry->pwszDNSName))); + break; + } + case CERT_ALT_NAME_DIRECTORY_NAME: + { + altNames.push_back(make_pair(AltNameDirectory, certNameToString(&entry->DirectoryName))); + break; + } + case CERT_ALT_NAME_URL: + { + altNames.push_back(make_pair(AltNameURL, Ice::wstringToString(entry->pwszURL))); + break; + } + case CERT_ALT_NAME_IP_ADDRESS: + { + if(entry->IPAddress.cbData == 4) + { + // + // IPv4 address + // + ostringstream os; + Byte* src = reinterpret_cast<Byte*>(entry->IPAddress.pbData); + for(int j = 0; j < 4;) + { + int value = 0; + Byte* dest = reinterpret_cast<Byte*>(&value); + *dest = *src++; + os << value; + if(++j < 4) + { + os << "."; + } + } + altNames.push_back(make_pair(AltNAmeIP, os.str())); + } + // + // TODO IPv6 Address support. + // + break; + } + default: + { + // Not supported + break; + } + } + } + LocalFree(altName); + } + return altNames; +} + +} // End anonymous namespace + +SCHannelX509ExtensionI::SCHannelX509ExtensionI(CERT_EXTENSION extension, const string& oid, const SChannel::CertificatePtr& cert) : + _extension(extension), + _oid(oid), + _cert(cert) +{ +} + +bool +SCHannelX509ExtensionI::isCritical() const +{ + return _extension.fCritical != 0; +} + +string +SCHannelX509ExtensionI::getOID() const +{ + return _oid; +} + +vector<Ice::Byte> +SCHannelX509ExtensionI::getData() const +{ + vector<Ice::Byte> data; + data.resize(_extension.Value.cbData); + memcpy(&data[0], _extension.Value.pbData, _extension.Value.cbData); + return data; +} + +SChannelCertificateI::SChannelCertificateI(CERT_SIGNED_CONTENT_INFO* cert) : + _cert(cert), + _certInfo(0) +{ + if(!_cert) + { + throw IceUtil::IllegalArgumentException(__FILE__, __LINE__, "Invalid certificate reference"); + } + + try + { + // + // Decode certificate info + // + DWORD length = 0; + if(!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_CERT_TO_BE_SIGNED, _cert->ToBeSigned.pbData, + _cert->ToBeSigned.cbData, CRYPT_DECODE_ALLOC_FLAG, 0, &_certInfo, &length)) + { + throw CertificateEncodingException(__FILE__, __LINE__, IceUtilInternal::lastErrorToString()); + } + } + catch(...) + { + LocalFree(_cert); + _cert = 0; + throw; + } +} + +SChannelCertificateI::~SChannelCertificateI() +{ + if(_cert) + { + LocalFree(_cert); + if(_certInfo) + { + LocalFree(_certInfo); + } + } +} + +bool +SChannelCertificateI::operator==(const IceSSL::Certificate& r) const +{ + const SChannelCertificateI* p = dynamic_cast<const SChannelCertificateI*>(&r); + if(!p) + { + return false; + } + + return CertCompareCertificate(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, _certInfo, p->_certInfo) != 0; +} + +vector<Ice::Byte> +SChannelCertificateI::getAuthorityKeyIdentifier() const +{ + vector<Ice::Byte> keyid; + PCERT_EXTENSION extension = CertFindExtension(szOID_AUTHORITY_KEY_IDENTIFIER2, _certInfo->cExtension, + _certInfo->rgExtension); + if(extension) + { + CERT_AUTHORITY_KEY_ID2_INFO* decoded; + DWORD length = 0; + if(!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_AUTHORITY_KEY_ID2, extension->Value.pbData, + extension->Value.cbData, CRYPT_DECODE_ALLOC_FLAG, 0, &decoded, &length)) + { + throw CertificateEncodingException(__FILE__, __LINE__, IceUtilInternal::lastErrorToString()); + } + + if(decoded->KeyId.pbData && decoded->KeyId.cbData) + { + keyid.resize(decoded->KeyId.cbData); + memcpy(&keyid[0], decoded->KeyId.pbData, decoded->KeyId.cbData); + LocalFree(decoded); + } + } + return keyid; +} + +vector<Ice::Byte> +SChannelCertificateI::getSubjectKeyIdentifier() const +{ + vector<Ice::Byte> keyid; + PCERT_EXTENSION extension = CertFindExtension(szOID_SUBJECT_KEY_IDENTIFIER, _certInfo->cExtension, + _certInfo->rgExtension); + if(extension) + { + CRYPT_DATA_BLOB* decoded; + DWORD length = 0; + if(!CryptDecodeObjectEx(X509_ASN_ENCODING, szOID_SUBJECT_KEY_IDENTIFIER, extension->Value.pbData, + extension->Value.cbData, CRYPT_DECODE_ALLOC_FLAG, 0, &decoded, &length)) + { + throw CertificateEncodingException(__FILE__, __LINE__, IceUtilInternal::lastErrorToString()); + } + + if(decoded->pbData && decoded->cbData) + { + keyid.resize(decoded->cbData); + memcpy(&keyid[0], decoded->pbData, decoded->cbData); + LocalFree(decoded); + } + } + return keyid; +} + +bool +SChannelCertificateI::verify(const CertificatePtr& cert) const +{ + bool result = false; + SChannelCertificateI* c = dynamic_cast<SChannelCertificateI*>(cert.get()); + if(c) + { + BYTE* buffer = 0; + DWORD length = 0; + if(!CryptEncodeObjectEx(X509_ASN_ENCODING, X509_CERT, _cert, CRYPT_ENCODE_ALLOC_FLAG , 0, &buffer, &length)) + { + throw CertificateEncodingException(__FILE__, __LINE__, IceUtilInternal::lastErrorToString()); + } + result = CryptVerifyCertificateSignature(0, X509_ASN_ENCODING, buffer, length, &c->_certInfo->SubjectPublicKeyInfo) != 0; + LocalFree(buffer); + } + return result; +} + +string +SChannelCertificateI::encode() const +{ + string s; + DWORD length = 0; + BYTE* buffer = 0; + try + { + if(!CryptEncodeObjectEx(X509_ASN_ENCODING, X509_CERT, _cert, CRYPT_ENCODE_ALLOC_FLAG , 0, &buffer, &length)) + { + throw CertificateEncodingException(__FILE__, __LINE__, IceUtilInternal::lastErrorToString()); + } + + DWORD encodedLength = 0; + if(!CryptBinaryToString(buffer, length, CRYPT_STRING_BASE64HEADER | CRYPT_STRING_NOCR, 0, &encodedLength)) + { + throw CertificateEncodingException(__FILE__, __LINE__, IceUtilInternal::lastErrorToString()); + } + + std::vector<char> encoded; + encoded.resize(encodedLength); + if(!CryptBinaryToString(buffer, length, CRYPT_STRING_BASE64HEADER | CRYPT_STRING_NOCR, &encoded[0], + &encodedLength)) + { + throw CertificateEncodingException(__FILE__, __LINE__, IceUtilInternal::lastErrorToString()); + } + LocalFree(buffer); + buffer = 0; + s.assign(&encoded[0]); + } + catch(...) + { + if(buffer) + { + LocalFree(buffer); + } + throw; + } + return s; +} + +# ifdef ICE_CPP11_MAPPING +chrono::system_clock::time_point +# else +IceUtil::Time +# endif +SChannelCertificateI::getNotAfter() const +{ + return filetimeToTime(_certInfo->NotAfter); +} + +# ifdef ICE_CPP11_MAPPING +chrono::system_clock::time_point +# else +IceUtil::Time +# endif +SChannelCertificateI::getNotBefore() const +{ + return filetimeToTime(_certInfo->NotBefore); +} + +string +SChannelCertificateI::getSerialNumber() const +{ + ostringstream os; + for(int i = _certInfo->SerialNumber.cbData - 1; i >= 0; --i) + { + unsigned char c = _certInfo->SerialNumber.pbData[i]; + os.fill('0'); + os.width(2); + os << hex << static_cast<int>(c); + if(i) + { + os << ' '; + } + } + return IceUtilInternal::toUpper(os.str()); +} + +DistinguishedName +SChannelCertificateI::getIssuerDN() const +{ + return DistinguishedName(certNameToString(&_certInfo->Issuer)); +} + +vector<pair<int, string> > +SChannelCertificateI::getIssuerAlternativeNames() const +{ + return certificateAltNames(_certInfo, szOID_ISSUER_ALT_NAME2); +} + +DistinguishedName +SChannelCertificateI::getSubjectDN() const +{ + return DistinguishedName(certNameToString(&_certInfo->Subject)); +} + +vector<pair<int, string> > +SChannelCertificateI::getSubjectAlternativeNames() const +{ + return certificateAltNames(_certInfo, szOID_SUBJECT_ALT_NAME2); +} + +int +SChannelCertificateI::getVersion() const +{ + return _certInfo->dwVersion; +} + +CERT_SIGNED_CONTENT_INFO* +SChannelCertificateI::getCert() const +{ + return _cert; +} + +void +SChannelCertificateI::loadX509Extensions() const +{ + IceUtil::Mutex::Lock sync(*this); + if(_extensions.empty()) + { + for(size_t i = 0; i < _certInfo->cExtension; ++i) + { + CERT_EXTENSION ext = _certInfo->rgExtension[i]; + _extensions.push_back(ICE_DYNAMIC_CAST(X509Extension, + ICE_MAKE_SHARED(SCHannelX509ExtensionI, ext, ext.pszObjId, + ICE_DYNAMIC_CAST(SChannel::Certificate, ICE_SHARED_FROM_CONST_THIS(SChannelCertificateI))))); + } + } +} + +SChannel::CertificatePtr +SChannel::Certificate::create(CERT_SIGNED_CONTENT_INFO* cert) +{ + return ICE_MAKE_SHARED(SChannelCertificateI, cert); +} + +SChannel::CertificatePtr +SChannel::Certificate::load(const std::string& file) +{ + CERT_SIGNED_CONTENT_INFO* cert; + loadCertificate(&cert, file); + return ICE_MAKE_SHARED(SChannelCertificateI, cert); +} + +SChannel::CertificatePtr +SChannel::Certificate::decode(const std::string& encoding) +{ + CERT_SIGNED_CONTENT_INFO* cert; + loadCertificate(&cert, encoding.c_str(), static_cast<DWORD>(encoding.size())); + return ICE_MAKE_SHARED(SChannelCertificateI, cert); +} |