diff options
author | Jose <jose@zeroc.com> | 2014-06-06 16:33:11 +0200 |
---|---|---|
committer | Jose <jose@zeroc.com> | 2014-06-06 16:33:11 +0200 |
commit | 7ba5b1fa9d9849182b19aebe5bad1570fb82452b (patch) | |
tree | 2b6a4f6407b4cc860d01f6e737959122a719ca3f /cpp/src | |
parent | Fixed ICE-5499, new garbage collection support (diff) | |
download | ice-7ba5b1fa9d9849182b19aebe5bad1570fb82452b.tar.bz2 ice-7ba5b1fa9d9849182b19aebe5bad1570fb82452b.tar.xz ice-7ba5b1fa9d9849182b19aebe5bad1570fb82452b.zip |
Fixed (ICE-4894) - Native SSL implementation for OS X
Diffstat (limited to 'cpp/src')
28 files changed, 5426 insertions, 1375 deletions
diff --git a/cpp/src/Ice/Network.h b/cpp/src/Ice/Network.h index f8928c11f7b..3bc5856aea6 100644 --- a/cpp/src/Ice/Network.h +++ b/cpp/src/Ice/Network.h @@ -168,7 +168,7 @@ public: { } - SOCKET fd() + SOCKET fd() const { return _fd; } diff --git a/cpp/src/Ice/PropertyNames.cpp b/cpp/src/Ice/PropertyNames.cpp index c9bc11dc890..c173ecfaa82 100644 --- a/cpp/src/Ice/PropertyNames.cpp +++ b/cpp/src/Ice/PropertyNames.cpp @@ -6,7 +6,7 @@ // ICE_LICENSE file included in this distribution. // // ********************************************************************** -// Generated by makeprops.py from file ./config/PropertyNames.xml, Wed Jun 4 19:01:29 2014 +// Generated by makeprops.py from file ../config/PropertyNames.xml, Thu Jun 5 21:19:31 2014 // IMPORTANT: Do not edit this file -- any edits made here will be lost! @@ -109,8 +109,7 @@ const IceInternal::Property IcePropsData[] = IceInternal::Property("Ice.IPv6", false, 0), IceInternal::Property("Ice.EventLog.Source", false, 0), IceInternal::Property("Ice.FactoryAssemblies", false, 0), - IceInternal::Property("Ice.GC", false, 0), - IceInternal::Property("Ice.GC.Interval", true, "Ice.GC"), + IceInternal::Property("Ice.CollectObjects", false, 0), IceInternal::Property("Ice.ImplicitContext", false, 0), IceInternal::Property("Ice.InitPlugins", false, 0), IceInternal::Property("Ice.LogFile", false, 0), @@ -155,7 +154,6 @@ const IceInternal::Property IcePropsData[] = IceInternal::Property("Ice.ThreadPool.Server.ThreadPriority", false, 0), IceInternal::Property("Ice.ThreadPriority", false, 0), IceInternal::Property("Ice.Trace.Admin.Properties", false, 0), - IceInternal::Property("Ice.Trace.GC", true, 0), IceInternal::Property("Ice.Trace.Locator", false, 0), IceInternal::Property("Ice.Trace.Network", false, 0), IceInternal::Property("Ice.Trace.Protocol", false, 0), @@ -841,12 +839,15 @@ const IceInternal::Property IceSSLPropsData[] = IceInternal::Property("IceSSL.Ciphers", false, 0), IceInternal::Property("IceSSL.DefaultDir", false, 0), IceInternal::Property("IceSSL.DH.*", false, 0), + IceInternal::Property("IceSSL.DHParams", false, 0), IceInternal::Property("IceSSL.EntropyDaemon", false, 0), IceInternal::Property("IceSSL.FindCert.*", false, 0), IceInternal::Property("IceSSL.ImportCert.*", false, 0), IceInternal::Property("IceSSL.InitOpenSSL", false, 0), IceInternal::Property("IceSSL.KeyFile", false, 0), IceInternal::Property("IceSSL.KeySet", false, 0), + IceInternal::Property("IceSSL.Keychain", false, 0), + IceInternal::Property("IceSSL.KeychainPassword", false, 0), IceInternal::Property("IceSSL.Keystore", false, 0), IceInternal::Property("IceSSL.KeystorePassword", false, 0), IceInternal::Property("IceSSL.KeystoreType", false, 0), @@ -855,6 +856,8 @@ const IceInternal::Property IceSSLPropsData[] = IceInternal::Property("IceSSL.PasswordRetryMax", false, 0), IceInternal::Property("IceSSL.PersistKeySet", false, 0), IceInternal::Property("IceSSL.Protocols", false, 0), + IceInternal::Property("IceSSL.ProtocolVersionMax", false, 0), + IceInternal::Property("IceSSL.ProtocolVersionMin", false, 0), IceInternal::Property("IceSSL.Random", false, 0), IceInternal::Property("IceSSL.Trace.Security", false, 0), IceInternal::Property("IceSSL.TrustOnly", false, 0), diff --git a/cpp/src/Ice/PropertyNames.h b/cpp/src/Ice/PropertyNames.h index 7c33f285caf..8637fb4f1cd 100644 --- a/cpp/src/Ice/PropertyNames.h +++ b/cpp/src/Ice/PropertyNames.h @@ -6,7 +6,7 @@ // ICE_LICENSE file included in this distribution. // // ********************************************************************** -// Generated by makeprops.py from file ./config/PropertyNames.xml, Wed Jun 4 19:01:29 2014 +// Generated by makeprops.py from file ../config/PropertyNames.xml, Thu Jun 5 21:19:31 2014 // IMPORTANT: Do not edit this file -- any edits made here will be lost! diff --git a/cpp/src/IceSSL/AcceptorI.cpp b/cpp/src/IceSSL/AcceptorI.cpp index 5780a9bae89..f835f6b8044 100644 --- a/cpp/src/IceSSL/AcceptorI.cpp +++ b/cpp/src/IceSSL/AcceptorI.cpp @@ -9,7 +9,9 @@ #include <IceSSL/AcceptorI.h> #include <IceSSL/Instance.h> +#include <IceSSL/SecureTransportTransceiverI.h> #include <IceSSL/TransceiverI.h> + #include <IceSSL/Util.h> #include <Ice/Communicator.h> @@ -144,7 +146,7 @@ IceSSL::AcceptorI::accept() // // The plug-in may not be initialized. // - if(!_instance->context()) + if(!_instance->initialized()) { PluginInitializationException ex(__FILE__, __LINE__); ex.reason = "IceSSL: plug-in is not initialized"; diff --git a/cpp/src/IceSSL/Certificate.cpp b/cpp/src/IceSSL/Certificate.cpp index 71ef9a9fd96..5c7f7bf08d7 100644 --- a/cpp/src/IceSSL/Certificate.cpp +++ b/cpp/src/IceSSL/Certificate.cpp @@ -16,12 +16,11 @@ #include <IceSSL/RFC2253.h> #include <Ice/Object.h> -#include <openssl/x509v3.h> -#include <openssl/pem.h> - -// Ignore OS X OpenSSL deprecation warnings -#ifdef __APPLE__ -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#ifdef ICE_USE_OPENSSL +# include <openssl/x509v3.h> +# include <openssl/pem.h> +#elif defined(ICE_USE_SECURE_TRANSPORT) +# include <Security/Security.h> #endif #ifdef __SUNPRO_CC @@ -46,6 +45,270 @@ using namespace IceSSL; const char* IceSSL::CertificateReadException::_name = "IceSSL::CertificateReadException"; +#ifdef ICE_USE_SECURE_TRANSPORT +// +// Map a certificate OID to its alias +// +struct CertificateOID +{ + const char* name; + const char* alias; +}; + +const CertificateOID certificateOIDS[] = +{ + {"2.5.4.3", "CN"}, + {"2.5.4.4", "SN"}, + {"2.5.4.5", "DeviceSerialNumber"}, + {"2.5.4.6", "C"}, + {"2.5.4.7", "L"}, + {"2.5.4.8", "ST"}, + {"2.5.4.9", "STREET"}, + {"2.5.4.10", "O"}, + {"2.5.4.11", "OU"}, + {"2.5.4.12", "T"}, + {"2.5.4.42", "G"}, + {"2.5.4.43", "I"}, + {"1.2.840.113549.1.9.8", "unstructuredAddress"}, + {"1.2.840.113549.1.9.2", "unstructuredName"}, + {"1.2.840.113549.1.9.1", "emailAddress"}, + {"0.9.2342.19200300.100.1.25", "DC"} +}; + +const int certificateOIDSSize = sizeof(certificateOIDS) / sizeof(CertificateOID); + +string +certificateOIDAlias(const string& name) +{ + for(int i = 0; i < certificateOIDSSize; ++i) + { + if(name == certificateOIDS[i].name) + { + return certificateOIDS[i].alias; + } + } + return name; +} + +// +// Map alternative name alias to its types. +// +const char* certificateAlternativeNameTypes[] = {"", "Email Address", "DNS Name", "", "Directory Name", "", "URI", + "IP Address"}; +const int certificateAlternativeNameTypesSize = sizeof(certificateAlternativeNameTypes) / sizeof(char*); + +int +certificateAlternativeNameType(const string& alias) +{ + if(!alias.empty()) + { + for(int i = 0; i < certificateAlternativeNameTypesSize; ++i) + { + if(alias == certificateAlternativeNameTypes[i]) + { + return i; + } + } + } + return -1; // Not supported +} + +string +scapeX509Name(const string& name) +{ + ostringstream os; + for(string::const_iterator i = name.begin(); i != name.end(); ++i) + { + switch(*i) + { + case ',': + case '=': + case '+': + case '<': + case '>': + case '#': + case ';': + { + os << '\\'; + } + default: + { + break; + } + } + os << *i; + } + return os.str(); +} + +DistinguishedName +getX509Name(SecCertificateRef cert, CFTypeRef key) +{ + CFErrorRef err = 0; + assert(key == kSecOIDX509V1IssuerName || key == kSecOIDX509V1SubjectName); + CFArrayRef keys = CFArrayCreate(NULL, &key , 1, &kCFTypeArrayCallBacks); + CFDictionaryRef values = SecCertificateCopyValues(cert, keys, &err); + CFRelease(keys); + + if(err) + { + ostringstream os; + os << "certificate error:\n" << errorToString(err); + CFRelease(err); + + CertificateEncodingException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + assert(values); + + CFArrayRef dn = (CFArrayRef)CFDictionaryGetValue((CFDictionaryRef)CFDictionaryGetValue(values, key), kSecPropertyKeyValue); + int size = CFArrayGetCount(dn); + list<pair<string, string> > rdnPairs; + for(int i = 0; i < size; ++i) + { + CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(dn, i); + rdnPairs.push_front(make_pair( + certificateOIDAlias(fromCFString((CFStringRef)CFDictionaryGetValue(dict, kSecPropertyKeyLabel))), + scapeX509Name(fromCFString((CFStringRef)CFDictionaryGetValue(dict, kSecPropertyKeyValue))))); + } + CFRelease(values); + return DistinguishedName(rdnPairs); +} + +vector<pair<int, string> > +getX509AltName(SecCertificateRef cert, CFTypeRef key) +{ + CFErrorRef err = 0; + assert(key == kSecOIDIssuerAltName || key == kSecOIDSubjectAltName); + CFArrayRef keys = CFArrayCreate(NULL, &key , 1, &kCFTypeArrayCallBacks); + CFDictionaryRef values = SecCertificateCopyValues(cert, keys, &err); + CFRelease(keys); + + if(err) + { + ostringstream os; + os << "certificate error:\n" << errorToString(err); + CFRelease(err); + + CertificateEncodingException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + assert(values); + + vector<pair<int, string> > pairs; + CFDictionaryRef property = (CFDictionaryRef)CFDictionaryGetValue(values, key); + if(property) + { + CFArrayRef names = (CFArrayRef)CFDictionaryGetValue(property, kSecPropertyKeyValue); + int size = CFArrayGetCount(names); + + for(int i = 0; i < size; ++i) + { + CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(names, i); + + int type = certificateAlternativeNameType(fromCFString( + (CFStringRef)CFDictionaryGetValue(dict, kSecPropertyKeyLabel))); + if(type != -1) + { + CFTypeRef v = (CFTypeRef)CFDictionaryGetValue(dict, kSecPropertyKeyValue); + CFStringRef t = (CFStringRef)CFDictionaryGetValue(dict, kSecPropertyKeyType); + if(CFEqual(t, kSecPropertyTypeString) || CFEqual(t, kSecPropertyTypeTitle)) + { + pairs.push_back(make_pair(type, fromCFString((CFStringRef)v))); + } + else if(CFEqual(t, kSecPropertyTypeURL)) + { + pairs.push_back(make_pair(type, fromCFString(CFURLGetString((CFURLRef)v)))); + } + else if(CFEqual(t, kSecPropertyTypeSection)) + { + CFArrayRef section = (CFArrayRef)v; + ostringstream os; + for(int i = 0, count = CFArrayGetCount(section); i < count;) + { + CFDictionaryRef d = (CFDictionaryRef)CFArrayGetValueAtIndex(section, i); + + CFStringRef sectionLabel = (CFStringRef)CFDictionaryGetValue(d, kSecPropertyKeyLabel); + CFStringRef sectionValue = (CFStringRef)CFDictionaryGetValue(d, kSecPropertyKeyValue); + + os << certificateOIDAlias(fromCFString(sectionLabel)) + << "=" << fromCFString(sectionValue); + if(++i < count) + { + os << ","; + } + } + pairs.push_back(make_pair(type, os.str())); + } + } + } + } + CFRelease(values); + return pairs; +} + +IceUtil::Time +getX509Date(SecCertificateRef cert, CFTypeRef key) +{ + assert(key == kSecOIDX509V1ValidityNotAfter || key == kSecOIDX509V1ValidityNotBefore); + CFErrorRef err = 0; + const void* keyValues[] = { key }; + CFArrayRef keys = CFArrayCreate(NULL, keyValues , 1, &kCFTypeArrayCallBacks); + CFDictionaryRef values = SecCertificateCopyValues(cert, keys, &err); + CFRelease(keys); + + if(err) + { + ostringstream os; + os << "certificate error:\n" << errorToString(err); + CFRelease(err); + + CertificateEncodingException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + assert(values); + + CFNumberRef date = (CFNumberRef)CFDictionaryGetValue( + (CFDictionaryRef)CFDictionaryGetValue(values, key), kSecPropertyKeyValue); + + CFAbsoluteTime seconds; + CFNumberGetValue(date, kCFNumberDoubleType, &seconds); + CFRelease(values); + return IceUtil::Time::secondsDouble(kCFAbsoluteTimeIntervalSince1970 + seconds); +} + +string +getX509String(SecCertificateRef cert, CFTypeRef key) +{ + assert(key == kSecOIDX509V1SerialNumber || key == kSecOIDX509V1Version); + CFErrorRef err = 0; + const void* keyValues[] = { key }; + CFArrayRef keys = CFArrayCreate(NULL, keyValues , 1, &kCFTypeArrayCallBacks); + CFDictionaryRef values = SecCertificateCopyValues(cert, keys, &err); + CFRelease(keys); + + if(err) + { + ostringstream os; + os << "certificate error:\n" << errorToString(err); + CFRelease(err); + + CertificateEncodingException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + assert(values); + + string value = fromCFString((CFStringRef)CFDictionaryGetValue( + (CFDictionaryRef)CFDictionaryGetValue(values, key), kSecPropertyKeyValue)); + CFRelease(values); + return value; +} +#endif + CertificateReadException::CertificateReadException(const char* file, int line, const string& r) : Exception(file, line), reason(r) @@ -104,6 +367,8 @@ CertificateEncodingException::ice_throw() const throw *this; } +#ifdef ICE_USE_OPENSSL + namespace { @@ -137,7 +402,7 @@ ASMUtcTimeToIceUtilTime(const ASN1_UTCTIME* s) memset(&tm, '\0', sizeof tm); -#define g2(p) (((p)[0]-'0')*10+(p)[1]-'0') +# define g2(p) (((p)[0]-'0')*10+(p)[1]-'0') tm.tm_year = g2(s->data); if(tm.tm_year < 50) tm.tm_year += 100; @@ -158,7 +423,7 @@ ASMUtcTimeToIceUtilTime(const ASN1_UTCTIME* s) offset = -offset; } } -#undef g2 +# undef g2 // // If timegm was on all systems this code could be @@ -176,7 +441,7 @@ ASMUtcTimeToIceUtilTime(const ASN1_UTCTIME* s) } static string -convertX509NameToString(X509NAME* name) +convertX509NameToString(X509_NAME* name) { BIO* out = BIO_new(BIO_s_mem()); X509_NAME_print_ex(out, name, 0, XN_FLAG_RFC2253); @@ -272,6 +537,7 @@ convertGeneralNames(GENERAL_NAMES* gens) sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); return alt; } +#endif const char* ParseException::_name = "IceSSL::ParseException"; @@ -303,12 +569,6 @@ ParseException::ice_throw() const throw *this; } -DistinguishedName::DistinguishedName(X509NAME* name) : - _rdns(RFC2253::parseStrict(convertX509NameToString(name))) -{ - unescape(); -} - DistinguishedName::DistinguishedName(const string& dn) : _rdns(RFC2253::parseStrict(dn)) { @@ -395,17 +655,21 @@ DistinguishedName::unescape() } } -PublicKey::PublicKey(EVP_PKEY* key) : +PublicKey::PublicKey(KeyRef key) : _key(key) { } PublicKey::~PublicKey() { +#ifdef ICE_USE_SECURE_TRANSPORT + CFRelease(_key); +#else EVP_PKEY_free(_key); +#endif } -EVP_PKEY* +KeyRef PublicKey::key() const { return _key; @@ -414,7 +678,7 @@ PublicKey::key() const // // The caller is responsible for incrementing the reference count. // -Certificate::Certificate(X509* cert) : +Certificate::Certificate(X509CertificateRef cert) : _cert(cert) { assert(_cert != 0); @@ -422,12 +686,21 @@ Certificate::Certificate(X509* cert) : Certificate::~Certificate() { +#ifdef ICE_USE_SECURE_TRANSPORT + CFRelease(_cert); +#else X509_free(_cert); +#endif } CertificatePtr Certificate::load(const string& file) { +#ifdef ICE_USE_SECURE_TRANSPORT + SecCertificateRef cert = 0; + loadCertificate(&cert, 0, 0, 0, file); + return new Certificate(cert); +#else BIO *cert = BIO_new(BIO_s_file()); if(BIO_read_filename(cert, file.c_str()) <= 0) { @@ -435,7 +708,7 @@ Certificate::load(const string& file) throw CertificateReadException(__FILE__, __LINE__, "error opening file"); } - X509* x = PEM_read_bio_X509_AUX(cert, NULL, NULL, NULL); + X509CertificateRef x = PEM_read_bio_X509_AUX(cert, NULL, NULL, NULL); if(x == NULL) { BIO_free(cert); @@ -443,13 +716,47 @@ Certificate::load(const string& file) } BIO_free(cert); return new Certificate(x); +#endif } CertificatePtr Certificate::decode(const string& encoding) { +#ifdef ICE_USE_SECURE_TRANSPORT + CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const UInt8*>(encoding.c_str()), + encoding.size(), kCFAllocatorNull); + + SecExternalFormat format = kSecFormatUnknown; + SecExternalItemType type = kSecItemTypeCertificate; + + SecItemImportExportKeyParameters params; + memset(¶ms, 0, sizeof(params)); + params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; + + CFArrayRef items = 0; + + OSStatus err = SecItemImport(data, 0, &format, &type, 0, ¶ms, 0, &items); + + CFRelease(data); + + if(err != noErr) + { + ostringstream os; + os << "error decoding certificate:\n" << errorToString(err); + CertificateReadException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + SecKeychainItemRef item = (SecKeychainItemRef)CFArrayGetValueAtIndex(items, 0); + + CFRetain(item); + CFRelease(items); + + assert(SecCertificateGetTypeID() == CFGetTypeID(item)); + return new Certificate((SecCertificateRef)item); +#else BIO *cert = BIO_new_mem_buf(static_cast<void*>(const_cast<char*>(&encoding[0])), static_cast<int>(encoding.size())); - X509* x = PEM_read_bio_X509_AUX(cert, NULL, NULL, NULL); + X509CertificateRef x = PEM_read_bio_X509_AUX(cert, NULL, NULL, NULL); if(x == NULL) { BIO_free(cert); @@ -457,35 +764,179 @@ Certificate::decode(const string& encoding) } BIO_free(cert); return new Certificate(x); +#endif } bool Certificate::operator==(const Certificate& other) const { +#ifdef ICE_USE_SECURE_TRANSPORT + return CFEqual(_cert, other._cert); +#else return X509_cmp(_cert, other._cert) == 0; +#endif } bool Certificate::operator!=(const Certificate& other) const { +#ifdef ICE_USE_SECURE_TRANSPORT + return !CFEqual(_cert, other._cert); +#else return X509_cmp(_cert, other._cert) != 0; +#endif } PublicKeyPtr Certificate::getPublicKey() const { +#ifdef ICE_USE_SECURE_TRANSPORT + SecKeyRef key; + OSStatus err = SecCertificateCopyPublicKey(_cert, &key); + if(err != noErr) + { + ostringstream os; + os << "certificate error:\n" << errorToString(err); + CertificateEncodingException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + return new PublicKey(key); +#else return new PublicKey(X509_get_pubkey(_cert)); +#endif +} + +bool +Certificate::verify(const CertificatePtr& cert) const +{ +#ifdef ICE_USE_SECURE_TRANSPORT + + bool valid = false; + + CFErrorRef error = 0; + CFDataRef issuer = 0; + CFDataRef subject = 0; + + issuer = SecCertificateCopyNormalizedIssuerContent(_cert, &error); + if(error) + { + ostringstream os; + os << "certificate error:\n" << errorToString(error); + CFRelease(error); + CertificateEncodingException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + subject = SecCertificateCopyNormalizedSubjectContent(cert->getCert(), &error); + if(error) + { + ostringstream os; + os << "certificate error:\n" << errorToString(error); + CFRelease(error); + CertificateEncodingException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + // + // The certificate issuer must match the CA subject. + // + valid = CFEqual(issuer, subject); + + CFRelease(issuer); + CFRelease(subject); + + if(valid) + { + SecPolicyRef policy = 0; + SecTrustRef trust = 0; + try + { + SecPolicyRef policy = SecPolicyCreateBasicX509(); + SecTrustResultType trustResult = kSecTrustResultInvalid; + SecTrustRef trust; + OSStatus err = SecTrustCreateWithCertificates(_cert, policy, &trust); + + if(err != noErr) + { + ostringstream os; + os << "certificate error:\n" << errorToString(err); + CertificateEncodingException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + SecCertificateRef certs[1] = { cert->getCert() }; + + CFArrayRef anchorCertificates = CFArrayCreate(kCFAllocatorDefault, (const void**)&certs, 1, &kCFTypeArrayCallBacks); + err = SecTrustSetAnchorCertificates(trust, anchorCertificates); + CFRelease(anchorCertificates); + + if(err != noErr) + { + ostringstream os; + os << "certificate error:\n" << errorToString(err); + CertificateEncodingException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + err = SecTrustEvaluate(trust, &trustResult); + if(err != noErr) + { + ostringstream os; + os << "certificate error:\n" << errorToString(err); + CertificateEncodingException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + valid = trustResult == kSecTrustResultUnspecified; + + CFRelease(policy); + CFRelease(trust); + } + catch(...) + { + if(policy) + { + CFRelease(policy); + } + + if(trust) + { + CFRelease(trust); + } + throw; + } + } + return valid; +#else + return X509_verify(_cert, cert->getPublicKey()->key()) > 0; +#endif } +#ifndef ICE_USE_SECURE_TRANSPORT bool Certificate::verify(const PublicKeyPtr& key) const { return X509_verify(_cert, key->key()) > 0; } +#endif string Certificate::encode() const { +#ifdef ICE_USE_SECURE_TRANSPORT + CFDataRef exported; + OSStatus err = SecItemExport(_cert, kSecFormatPEMSequence, kSecItemPemArmour, 0, &exported); + if(err != noErr) + { + ostringstream os; + os << "error encoding certificate:\n" << errorToString(err); + CertificateEncodingException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + string data(reinterpret_cast<const char*>(CFDataGetBytePtr(exported)), CFDataGetLength(exported)); + CFRelease(exported); + return data; +#else BIO* out = BIO_new(BIO_s_mem()); int i = PEM_write_bio_X509_AUX(out, _cert); if(i <= 0) @@ -498,6 +949,7 @@ Certificate::encode() const string result = string(p->data, p->length); BIO_free(out); return result; +#endif } bool @@ -516,18 +968,29 @@ Certificate::checkValidity(const IceUtil::Time& now) const IceUtil::Time Certificate::getNotAfter() const { +#ifdef ICE_USE_SECURE_TRANSPORT + return getX509Date(_cert, kSecOIDX509V1ValidityNotAfter); +#else return ASMUtcTimeToIceUtilTime(X509_get_notAfter(_cert)); +#endif } IceUtil::Time Certificate::getNotBefore() const { +#ifdef ICE_USE_SECURE_TRANSPORT + return getX509Date(_cert, kSecOIDX509V1ValidityNotBefore); +#else return ASMUtcTimeToIceUtilTime(X509_get_notBefore(_cert)); +#endif } string Certificate::getSerialNumber() const { +#ifdef ICE_USE_SECURE_TRANSPORT + return getX509String(_cert, kSecOIDX509V1SerialNumber); +#else BIGNUM* bn = ASN1_INTEGER_to_BN(X509_get_serialNumber(_cert), 0); char* dec = BN_bn2dec(bn); string result = dec; @@ -535,6 +998,7 @@ Certificate::getSerialNumber() const BN_free(bn); return result; +#endif } //string @@ -550,33 +1014,53 @@ Certificate::getSerialNumber() const DistinguishedName Certificate::getIssuerDN() const { - return DistinguishedName(X509_get_issuer_name(_cert)); +#ifdef ICE_USE_SECURE_TRANSPORT + return getX509Name(_cert, kSecOIDX509V1IssuerName); +#else + return DistinguishedName(RFC2253::parseStrict(convertX509NameToString(X509_get_issuer_name(_cert)))); +#endif } vector<pair<int, string> > Certificate::getIssuerAlternativeNames() { +#ifdef ICE_USE_SECURE_TRANSPORT + return getX509AltName(_cert, kSecOIDIssuerAltName); +#else return convertGeneralNames(reinterpret_cast<GENERAL_NAMES*>( X509_get_ext_d2i(_cert, NID_issuer_alt_name, 0, 0))); +#endif } DistinguishedName Certificate::getSubjectDN() const { - return DistinguishedName(X509_get_subject_name(_cert)); +#ifdef ICE_USE_SECURE_TRANSPORT + return getX509Name(_cert, kSecOIDX509V1SubjectName); +#else + return DistinguishedName(RFC2253::parseStrict(convertX509NameToString(X509_get_subject_name(_cert)))); +#endif } vector<pair<int, string> > Certificate::getSubjectAlternativeNames() { +#ifdef ICE_USE_SECURE_TRANSPORT + return getX509AltName(_cert, kSecOIDSubjectAltName); +#else return convertGeneralNames( reinterpret_cast<GENERAL_NAMES*>(X509_get_ext_d2i(_cert, NID_subject_alt_name, 0, 0))); +#endif } int Certificate::getVersion() const { +#ifdef ICE_USE_SECURE_TRANSPORT + return atoi(getX509String(_cert, kSecOIDX509V1Version).c_str()); +#else return static_cast<int>(X509_get_version(_cert)); +#endif } string @@ -588,11 +1072,10 @@ Certificate::toString() const os << "subject: " << string(getSubjectDN()) << "\n"; os << "notBefore: " << getNotBefore().toDateTime() << "\n"; os << "notAfter: " << getNotAfter().toDateTime(); - return os.str(); } -X509* +X509CertificateRef Certificate::getCert() const { return _cert; diff --git a/cpp/src/IceSSL/ConnectorI.cpp b/cpp/src/IceSSL/ConnectorI.cpp index fb9386d0a89..74db5e52cb6 100644 --- a/cpp/src/IceSSL/ConnectorI.cpp +++ b/cpp/src/IceSSL/ConnectorI.cpp @@ -9,6 +9,7 @@ #include <IceSSL/ConnectorI.h> #include <IceSSL/Instance.h> +#include <IceSSL/SecureTransportTransceiverI.h> #include <IceSSL/TransceiverI.h> #include <IceSSL/EndpointI.h> #include <IceSSL/Util.h> @@ -26,7 +27,7 @@ IceSSL::ConnectorI::connect() // // The plug-in may not be initialized. // - if(!_instance->context()) + if(!_instance->initialized()) { PluginInitializationException ex(__FILE__, __LINE__); ex.reason = "IceSSL: plug-in is not initialized"; diff --git a/cpp/src/IceSSL/EndpointI.cpp b/cpp/src/IceSSL/EndpointI.cpp index de769b1186e..93994804783 100644 --- a/cpp/src/IceSSL/EndpointI.cpp +++ b/cpp/src/IceSSL/EndpointI.cpp @@ -381,5 +381,5 @@ IceSSL::EndpointFactoryI::destroy() IceInternal::EndpointFactoryPtr IceSSL::EndpointFactoryI::clone(const IceInternal::ProtocolInstancePtr& instance) const { - return new EndpointFactoryI(new Instance(_instance->sharedInstance(), instance->type(), instance->protocol())); + return new EndpointFactoryI(new Instance(_instance->engine(), instance->type(), instance->protocol())); } diff --git a/cpp/src/IceSSL/Instance.cpp b/cpp/src/IceSSL/Instance.cpp index 44c4704b2bc..3d3f24e58d8 100644 --- a/cpp/src/IceSSL/Instance.cpp +++ b/cpp/src/IceSSL/Instance.cpp @@ -11,1143 +11,18 @@ #ifdef _WIN32 # include <winsock2.h> #endif - #include <IceSSL/Instance.h> -#include <IceSSL/Util.h> -#include <IceSSL/TrustManager.h> - -#include <Ice/Communicator.h> -#include <Ice/LocalException.h> -#include <Ice/Logger.h> -#include <Ice/LoggerUtil.h> #include <Ice/Properties.h> -#include <IceUtil/Mutex.h> -#include <IceUtil/MutexPtrLock.h> -#include <IceUtil/StringUtil.h> - -#include <openssl/rand.h> -#include <openssl/err.h> - -#include <IceUtil/DisableWarnings.h> - -// Ignore OS X OpenSSL deprecation warnings -#ifdef __APPLE__ -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif - using namespace std; using namespace Ice; using namespace IceSSL; IceUtil::Shared* IceSSL::upCast(IceSSL::Instance* p) { return p; } -IceUtil::Shared* IceSSL::upCast(IceSSL::SharedInstance* p) { return p; } - -namespace -{ - -IceUtil::Mutex* staticMutex = 0; -int instanceCount = 0; -IceUtil::Mutex* locks = 0; - -class Init -{ -public: - - Init() - { - staticMutex = new IceUtil::Mutex; - } - - ~Init() - { - delete staticMutex; - staticMutex = 0; - if(locks) - { - delete[] locks; - locks = 0; - } - } -}; - -Init init; - -} - -extern "C" -{ - -// -// OpenSSL mutex callback. -// -void -IceSSL_opensslLockCallback(int mode, int n, const char* /*file*/, int /*line*/) -{ - assert(locks); - if(mode & CRYPTO_LOCK) - { - locks[n].lock(); - } - else - { - locks[n].unlock(); - } -} - -// -// OpenSSL thread id callback. -// -unsigned long -IceSSL_opensslThreadIdCallback() -{ -#if defined(_WIN32) - return static_cast<unsigned long>(GetCurrentThreadId()); -#elif defined(__FreeBSD__) || defined(__APPLE__) || defined(__osf1__) - // - // On some platforms, pthread_t is a pointer to a per-thread structure. - // - return reinterpret_cast<unsigned long>(pthread_self()); -#elif (defined(__linux) || defined(__sun) || defined(__hpux)) || defined(_AIX) - // - // On Linux, Solaris, HP-UX and AIX, pthread_t is an integer. - // - return static_cast<unsigned long>(pthread_self()); -#else -# error "Unknown platform" -#endif -} - -int -IceSSL_opensslPasswordCallback(char* buf, int size, int flag, void* userData) -{ - IceSSL::SharedInstance* p = reinterpret_cast<IceSSL::SharedInstance*>(userData); - string passwd = p->password(flag == 1); - int sz = static_cast<int>(passwd.size()); - if(sz > size) - { - sz = size - 1; - } - strncpy(buf, passwd.c_str(), sz); - buf[sz] = '\0'; - - for(string::iterator i = passwd.begin(); i != passwd.end(); ++i) - { - *i = '\0'; - } - - return sz; -} - -#ifndef OPENSSL_NO_DH -DH* -IceSSL_opensslDHCallback(SSL* ssl, int /*isExport*/, int keyLength) -{ - IceSSL::SharedInstance* p = reinterpret_cast<IceSSL::SharedInstance*>(SSL_CTX_get_ex_data(ssl->ctx, 0)); - return p->dhParams(keyLength); -} -#endif - -int -IceSSL_opensslVerifyCallback(int ok, X509_STORE_CTX* ctx) -{ - SSL* ssl = reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); - IceSSL::SharedInstance* p = reinterpret_cast<IceSSL::SharedInstance*>(SSL_CTX_get_ex_data(ssl->ctx, 0)); - return p->verifyCallback(ok, ssl, ctx); -} - -} - -static bool -passwordError() -{ - int reason = ERR_GET_REASON(ERR_peek_error()); - return (reason == PEM_R_BAD_BASE64_DECODE || - reason == PEM_R_BAD_DECRYPT || - reason == PEM_R_BAD_PASSWORD_READ || - reason == PEM_R_PROBLEMS_GETTING_PASSWORD); -} - -IceSSL::SharedInstance::SharedInstance(const CommunicatorPtr& communicator) : - _communicator(communicator), - _logger(communicator->getLogger()), - _initialized(false), - _ctx(0) -{ - __setNoDelete(true); - - // - // Initialize OpenSSL if necessary. - // - IceUtilInternal::MutexPtrLock<IceUtil::Mutex> sync(staticMutex); - instanceCount++; - - if(instanceCount == 1) - { - PropertiesPtr properties = communicator->getProperties(); - - // - // The IceSSL.InitOpenSSL property specifies whether we should perform the global - // startup (and shutdown) tasks for the OpenSSL library. - // - // If an application uses multiple components that each depend on OpenSSL, the - // application should disable OpenSSL initialization in those components and - // perform the initialization itself. - // - _initOpenSSL = properties->getPropertyAsIntWithDefault("IceSSL.InitOpenSSL", 1) > 0; - if(_initOpenSSL) - { - // - // Create the mutexes and set the callbacks. - // - if(!locks) - { - locks = new IceUtil::Mutex[CRYPTO_num_locks()]; - CRYPTO_set_locking_callback(IceSSL_opensslLockCallback); - CRYPTO_set_id_callback(IceSSL_opensslThreadIdCallback); - } - - // - // Load human-readable error messages. - // - SSL_load_error_strings(); - - // - // Initialize the SSL library. - // - SSL_library_init(); - - // - // This is necessary to allow programs that use OpenSSL 0.9.x to - // load private key files generated by OpenSSL 1.x. - // - OpenSSL_add_all_algorithms(); - - // - // Initialize the PRNG. - // -#ifdef WINDOWS - RAND_screen(); // Uses data from the screen if possible. -#endif - char randFile[1024]; - if(RAND_file_name(randFile, sizeof(randFile))) // Gets the name of a default seed file. - { - RAND_load_file(randFile, 1024); - } - - string randFiles = properties->getProperty("IceSSL.Random"); - - if(!randFiles.empty()) - { - vector<string> files; -#ifdef _WIN32 - const string sep = ";"; -#else - const string sep = ":"; -#endif - string defaultDir = properties->getProperty("IceSSL.DefaultDir"); - - if(!IceUtilInternal::splitString(randFiles, sep, files)) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: invalid value for IceSSL.Random:\n" + randFiles; - throw ex; - } - for(vector<string>::iterator p = files.begin(); p != files.end(); ++p) - { - string file = *p; - if(!checkPath(file, defaultDir, false)) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: entropy data file not found:\n" + file; - throw ex; - } - if(!RAND_load_file(file.c_str(), 1024)) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: unable to load entropy data from " + file; - throw ex; - } - } - } -#ifndef _WIN32 - // - // The Entropy Gathering Daemon (EGD) is not available on Windows. - // The file should be a Unix domain socket for the daemon. - // - string entropyDaemon = properties->getProperty("IceSSL.EntropyDaemon"); - if(!entropyDaemon.empty()) - { - if(RAND_egd(entropyDaemon.c_str()) <= 0) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: EGD failure using file " + entropyDaemon; - throw ex; - } - } -#endif - if(!RAND_status()) - { - communicator->getLogger()->warning("IceSSL: insufficient data to initialize PRNG"); - } - } - else - { - if(!properties->getProperty("IceSSL.Random").empty()) - { - _logger->warning("IceSSL: ignoring IceSSL.Random because OpenSSL initialization is disabled"); - } -#ifndef _WIN32 - else if(!properties->getProperty("IceSSL.EntropyDaemon").empty()) - { - _logger->warning("IceSSL: ignoring IceSSL.EntropyDaemon because OpenSSL initialization is disabled"); - } -#endif - } - } - - _securityTraceLevel = communicator->getProperties()->getPropertyAsInt("IceSSL.Trace.Security"); - _securityTraceCategory = "Security"; - _trustManager = new TrustManager(communicator); - - __setNoDelete(false); -} - -IceSSL::SharedInstance::~SharedInstance() -{ - // - // Clean up OpenSSL resources. - // - IceUtilInternal::MutexPtrLock<IceUtil::Mutex> sync(staticMutex); - - if(--instanceCount == 0 && _initOpenSSL) - { - // - // NOTE: We can't destroy the locks here: threads which might have called openssl methods - // might access openssl locks upon termination (from DllMain/THREAD_DETACHED). Instead, - // we release the locks in the ~Init() static destructor. See bug #4156. - // - //CRYPTO_set_locking_callback(0); - //CRYPTO_set_id_callback(0); - //delete[] locks; - //locks = 0; - - CRYPTO_cleanup_all_ex_data(); - RAND_cleanup(); - ERR_free_strings(); - EVP_cleanup(); - } -} - -void -IceSSL::SharedInstance::initialize() -{ - if(_initialized) - { - return; - } - - try - { - const string propPrefix = "IceSSL."; - PropertiesPtr properties = communicator()->getProperties(); - - // - // CheckCertName determines whether we compare the name in a peer's - // certificate against its hostname. - // - _checkCertName = properties->getPropertyAsIntWithDefault(propPrefix + "CheckCertName", 0) > 0; - - // - // VerifyDepthMax establishes the maximum length of a peer's certificate - // chain, including the peer's certificate. A value of 0 means there is - // no maximum. - // - _verifyDepthMax = properties->getPropertyAsIntWithDefault(propPrefix + "VerifyDepthMax", 2); - - // - // VerifyPeer determines whether certificate validation failures abort a connection. - // - _verifyPeer = properties->getPropertyAsIntWithDefault(propPrefix + "VerifyPeer", 2); - - // - // Protocols selects which protocols to enable. - // - const int protocols = parseProtocols(properties->getPropertyAsList(propPrefix + "Protocols")); - - // - // Create an SSL context if the application hasn't supplied one. - // - if(!_ctx) - { - _ctx = SSL_CTX_new(getMethod(protocols)); - if(!_ctx) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: unable to create SSL context:\n" + sslErrors(); - throw ex; - } - - // - // Check for a default directory. We look in this directory for - // files mentioned in the configuration. - // - string defaultDir = properties->getProperty(propPrefix + "DefaultDir"); - - // - // If the configuration defines a password, or the application has supplied - // a password prompt object, then register a password callback. Otherwise, - // let OpenSSL use its default behavior. - // - { - // TODO: Support quoted value? - string password = properties->getProperty(propPrefix + "Password"); - if(!password.empty() || _prompt) - { - SSL_CTX_set_default_passwd_cb(_ctx, IceSSL_opensslPasswordCallback); - SSL_CTX_set_default_passwd_cb_userdata(_ctx, this); - _password = password; - } - } - - int passwordRetryMax = properties->getPropertyAsIntWithDefault(propPrefix + "PasswordRetryMax", 3); - - // - // Establish the location of CA certificates. - // - { - string caFile = properties->getProperty(propPrefix + "CertAuthFile"); - string caDir = properties->getPropertyWithDefault(propPrefix + "CertAuthDir", defaultDir); - const char* file = 0; - const char* dir = 0; - if(!caFile.empty()) - { - if(!checkPath(caFile, defaultDir, false)) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: CA certificate file not found:\n" + caFile; - throw ex; - } - file = caFile.c_str(); - } - if(!caDir.empty()) - { - if(!checkPath(caDir, defaultDir, true)) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: CA certificate directory not found:\n" + caDir; - throw ex; - } - dir = caDir.c_str(); - } - if(file || dir) - { - // - // The certificate may be stored in an encrypted file, so handle - // password retries. - // - int count = 0; - int err = 0; - while(count < passwordRetryMax) - { - ERR_clear_error(); - err = SSL_CTX_load_verify_locations(_ctx, file, dir); - if(err) - { - break; - } - ++count; - } - if(err == 0) - { - string msg = "IceSSL: unable to establish CA certificates"; - if(passwordError()) - { - msg += ":\ninvalid password"; - } - else - { - string err = sslErrors(); - if(!err.empty()) - { - msg += ":\n" + err; - } - } - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = msg; - throw ex; - } - } - } - - // - // Establish the certificate chains and private keys. One RSA certificate and - // one DSA certificate are allowed. - // - { -#ifdef _WIN32 - const string sep = ";"; -#else - const string sep = ":"; -#endif - string certFile = properties->getProperty(propPrefix + "CertFile"); - string keyFile = properties->getProperty(propPrefix + "KeyFile"); - vector<string>::size_type numCerts = 0; - if(!certFile.empty()) - { - vector<string> files; - if(!IceUtilInternal::splitString(certFile, sep, files) || files.size() > 2) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: invalid value for " + propPrefix + "CertFile:\n" + certFile; - throw ex; - } - numCerts = files.size(); - for(vector<string>::iterator p = files.begin(); p != files.end(); ++p) - { - string file = *p; - if(!checkPath(file, defaultDir, false)) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: certificate file not found:\n" + file; - throw ex; - } - // - // The certificate may be stored in an encrypted file, so handle - // password retries. - // - int count = 0; - int err = 0; - while(count < passwordRetryMax) - { - ERR_clear_error(); - err = SSL_CTX_use_certificate_chain_file(_ctx, file.c_str()); - if(err) - { - break; - } - ++count; - } - if(err == 0) - { - string msg = "IceSSL: unable to load certificate chain from file " + file; - if(passwordError()) - { - msg += ":\ninvalid password"; - } - else - { - string err = sslErrors(); - if(!err.empty()) - { - msg += ":\n" + err; - } - } - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = msg; - throw ex; - } - } - } - if(keyFile.empty()) - { - keyFile = certFile; // Assume the certificate file also contains the private key. - } - if(!keyFile.empty()) - { - vector<string> files; - if(!IceUtilInternal::splitString(keyFile, sep, files) || files.size() > 2) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: invalid value for " + propPrefix + "KeyFile:\n" + keyFile; - throw ex; - } - if(files.size() != numCerts) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: " + propPrefix + "KeyFile does not agree with " + propPrefix + "CertFile"; - throw ex; - } - for(vector<string>::iterator p = files.begin(); p != files.end(); ++p) - { - string file = *p; - if(!checkPath(file, defaultDir, false)) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: key file not found:\n" + file; - throw ex; - } - // - // The private key may be stored in an encrypted file, so handle - // password retries. - // - int count = 0; - int err = 0; - while(count < passwordRetryMax) - { - ERR_clear_error(); - err = SSL_CTX_use_PrivateKey_file(_ctx, file.c_str(), SSL_FILETYPE_PEM); - if(err) - { - break; - } - ++count; - } - if(err == 0) - { - string msg = "IceSSL: unable to load private key from file " + file; - if(passwordError()) - { - msg += ":\ninvalid password"; - } - else - { - string err = sslErrors(); - if(!err.empty()) - { - msg += ":\n" + err; - } - } - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = msg; - throw ex; - } - } - if(!SSL_CTX_check_private_key(_ctx)) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: unable to validate private key(s):\n" + sslErrors(); - throw ex; - } - } - } - - // - // Diffie Hellman configuration. - // - { -#ifndef OPENSSL_NO_DH - _dhParams = new DHParams; - SSL_CTX_set_options(_ctx, SSL_OP_SINGLE_DH_USE); - SSL_CTX_set_tmp_dh_callback(_ctx, IceSSL_opensslDHCallback); -#endif - // - // Properties have the following form: - // - // ...DH.<keyLength>=file - // - const string dhPrefix = propPrefix + "DH."; - PropertyDict d = properties->getPropertiesForPrefix(dhPrefix); - if(!d.empty()) - { -#ifdef OPENSSL_NO_DH - _logger->warning("IceSSL: OpenSSL is not configured for Diffie Hellman"); -#else - for(PropertyDict::iterator p = d.begin(); p != d.end(); ++p) - { - string s = p->first.substr(dhPrefix.size()); - int keyLength = atoi(s.c_str()); - if(keyLength > 0) - { - string file = p->second; - if(!checkPath(file, defaultDir, false)) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: DH parameter file not found:\n" + file; - throw ex; - } - if(!_dhParams->add(keyLength, file)) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: unable to read DH parameter file " + file; - throw ex; - } - } - } -#endif - } - } - } - - // - // Store a pointer to ourself for use in OpenSSL callbacks. - // - SSL_CTX_set_ex_data(_ctx, 0, this); - - // - // This is necessary for successful interop with Java. Without it, a Java - // client would fail to reestablish a connection: the server gets the - // error "session id context uninitialized" and the client receives - // "SSLHandshakeException: Remote host closed connection during handshake". - // - SSL_CTX_set_session_cache_mode(_ctx, SSL_SESS_CACHE_OFF); - - // - // Although we disable session caching, we still need to set a session ID - // context (ICE-5103). The value can be anything; here we just use the - // pointer to this SharedInstance object. - // - SSL_CTX_set_session_id_context(_ctx, reinterpret_cast<unsigned char*>(this), - static_cast<unsigned int>(sizeof(this))); - - // - // Select protocols. - // - if(protocols != 0) - { - setOptions(protocols); - } - - // - // Establish the cipher list. - // - string ciphers = properties->getProperty(propPrefix + "Ciphers"); - if(!ciphers.empty()) - { - if(!SSL_CTX_set_cipher_list(_ctx, ciphers.c_str())) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: unable to set ciphers using `" + ciphers + "':\n" + sslErrors(); - throw ex; - } - } - - // - // Determine whether a certificate is required from the peer. - // - { - int sslVerifyMode; - switch(_verifyPeer) - { - case 0: - sslVerifyMode = SSL_VERIFY_NONE; - break; - case 1: - sslVerifyMode = SSL_VERIFY_PEER; - break; - case 2: - sslVerifyMode = SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT; - break; - default: - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: invalid value for " + propPrefix + "VerifyPeer"; - throw ex; - } - } - SSL_CTX_set_verify(_ctx, sslVerifyMode, IceSSL_opensslVerifyCallback); - } - } - catch(...) - { - // - // We free the SSL context regardless of whether the plugin created it - // or the application supplied it. - // - SSL_CTX_free(_ctx); - _ctx = 0; - throw; - } - - _initialized = true; -} - -void -IceSSL::SharedInstance::context(SSL_CTX* context) -{ - if(_initialized) - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: plug-in is already initialized"; - throw ex; - } - - assert(!_ctx); - _ctx = context; -} - -SSL_CTX* -IceSSL::SharedInstance::context() const -{ - return _ctx; -} - -void -IceSSL::SharedInstance::setCertificateVerifier(const CertificateVerifierPtr& verifier) -{ - _verifier = verifier; -} - -void -IceSSL::SharedInstance::setPasswordPrompt(const PasswordPromptPtr& prompt) -{ - _prompt = prompt; -} - -CommunicatorPtr -IceSSL::SharedInstance::communicator() const -{ - return _communicator; -} - -void -IceSSL::SharedInstance::verifyPeer(SSL* ssl, SOCKET fd, const string& address, const NativeConnectionInfoPtr& info) -{ - long result = SSL_get_verify_result(ssl); - if(result != X509_V_OK) - { - if(_verifyPeer == 0) - { - if(_securityTraceLevel >= 1) - { - ostringstream ostr; - ostr << "IceSSL: ignoring certificate verification failure:\n" << X509_verify_cert_error_string(result); - _logger->trace(_securityTraceCategory, ostr.str()); - } - } - else - { - ostringstream ostr; - ostr << "IceSSL: certificate verification failed:\n" << X509_verify_cert_error_string(result); - string msg = ostr.str(); - if(_securityTraceLevel >= 1) - { - _logger->trace(_securityTraceCategory, msg); - } - SecurityException ex(__FILE__, __LINE__); - ex.reason = msg; - throw ex; - } - } - - X509* rawCert = SSL_get_peer_certificate(ssl); - CertificatePtr cert; - if(rawCert != 0) - { - cert = new Certificate(rawCert); - } - - // - // For an outgoing connection, we compare the proxy address (if any) against - // fields in the server's certificate (if any). - // - if(cert && !address.empty()) - { - // - // Extract the IP addresses and the DNS names from the subject - // alternative names. - // - vector<pair<int, string> > subjectAltNames = cert->getSubjectAlternativeNames(); - vector<string> ipAddresses; - vector<string> dnsNames; - for(vector<pair<int, string> >::const_iterator p = subjectAltNames.begin(); p != subjectAltNames.end(); ++p) - { - if(p->first == 7) - { - ipAddresses.push_back(IceUtilInternal::toLower(p->second)); - } - else if(p->first == 2) - { - dnsNames.push_back(IceUtilInternal::toLower(p->second)); - } - } - - // - // Compare the peer's address against the common name. - // - bool certNameOK = false; - string dn; - string addrLower = IceUtilInternal::toLower(address); - { - DistinguishedName d = cert->getSubjectDN(); - dn = IceUtilInternal::toLower(string(d)); - string cn = "cn=" + addrLower; - string::size_type pos = dn.find(cn); - if(pos != string::npos) - { - // - // Ensure we match the entire common name. - // - certNameOK = (pos + cn.size() == dn.size()) || (dn[pos + cn.size()] == ','); - } - } - - // - // Compare the peer's address against the dnsName and ipAddress - // values in the subject alternative name. - // - if(!certNameOK) - { - certNameOK = find(ipAddresses.begin(), ipAddresses.end(), addrLower) != ipAddresses.end(); - } - if(!certNameOK) - { - certNameOK = find(dnsNames.begin(), dnsNames.end(), addrLower) != dnsNames.end(); - } - - // - // Log a message if the name comparison fails. If CheckCertName is defined, - // we also raise an exception to abort the connection. Don't log a message if - // CheckCertName is not defined and a verifier is present. - // - if(!certNameOK && (_checkCertName || (_securityTraceLevel >= 1 && !_verifier))) - { - ostringstream ostr; - ostr << "IceSSL: "; - if(!_checkCertName) - { - ostr << "ignoring "; - } - ostr << "certificate validation failure:\npeer certificate does not have `" << address - << "' as its commonName or in its subjectAltName extension"; - if(!dn.empty()) - { - ostr << "\nSubject DN: " << dn; - } - if(!dnsNames.empty()) - { - ostr << "\nDNS names found in certificate: "; - for(vector<string>::const_iterator p = dnsNames.begin(); p != dnsNames.end(); ++p) - { - if(p != dnsNames.begin()) - { - ostr << ", "; - } - ostr << *p; - } - } - if(!ipAddresses.empty()) - { - ostr << "\nIP addresses found in certificate: "; - for(vector<string>::const_iterator p = ipAddresses.begin(); p != ipAddresses.end(); ++p) - { - if(p != ipAddresses.begin()) - { - ostr << ", "; - } - ostr << *p; - } - } - string msg = ostr.str(); - if(_securityTraceLevel >= 1) - { - Trace out(_logger, _securityTraceCategory); - out << msg; - } - if(_checkCertName) - { - SecurityException ex(__FILE__, __LINE__); - ex.reason = msg; - throw ex; - } - } - } - - if(_verifyDepthMax > 0 && static_cast<int>(info->certs.size()) > _verifyDepthMax) - { - ostringstream ostr; - ostr << (info->incoming ? "incoming" : "outgoing") << " connection rejected:\n" - << "length of peer's certificate chain (" << info->certs.size() << ") exceeds maximum of " - << _verifyDepthMax; - string msg = ostr.str(); - if(_securityTraceLevel >= 1) - { - _logger->trace(_securityTraceCategory, msg + "\n" + IceInternal::fdToString(fd)); - } - SecurityException ex(__FILE__, __LINE__); - ex.reason = msg; - throw ex; - } - - if(!_trustManager->verify(info)) - { - string msg = string(info->incoming ? "incoming" : "outgoing") + " connection rejected by trust manager"; - if(_securityTraceLevel >= 1) - { - _logger->trace(_securityTraceCategory, msg + "\n" + IceInternal::fdToString(fd)); - } - SecurityException ex(__FILE__, __LINE__); - ex.reason = msg; - throw ex; - } - - if(_verifier && !_verifier->verify(info)) - { - string msg = string(info->incoming ? "incoming" : "outgoing") + " connection rejected by certificate verifier"; - if(_securityTraceLevel >= 1) - { - _logger->trace(_securityTraceCategory, msg + "\n" + IceInternal::fdToString(fd)); - } - SecurityException ex(__FILE__, __LINE__); - ex.reason = msg; - throw ex; - } -} - -string -IceSSL::SharedInstance::sslErrors() const -{ - return getSslErrors(_securityTraceLevel >= 1); -} - -void -IceSSL::SharedInstance::destroy() -{ - if(_ctx) - { - SSL_CTX_free(_ctx); - } -} - -string -IceSSL::SharedInstance::password(bool /*encrypting*/) -{ - if(_prompt) - { - try - { - return _prompt->getPassword(); - } - catch(...) - { - // - // Don't allow exceptions to cross an OpenSSL boundary. - // - return string(); - } - } - else - { - return _password; - } -} - -int -IceSSL::SharedInstance::verifyCallback(int ok, SSL* ssl, X509_STORE_CTX* c) -{ - if(!ok && _securityTraceLevel >= 1) - { - X509* cert = X509_STORE_CTX_get_current_cert(c); - int err = X509_STORE_CTX_get_error(c); - char buf[256]; - - Trace out(_logger, _securityTraceCategory); - out << "certificate verification failure\n"; - - X509_NAME_oneline(X509_get_issuer_name(cert), buf, static_cast<int>(sizeof(buf))); - out << "issuer = " << buf << '\n'; - X509_NAME_oneline(X509_get_subject_name(cert), buf, static_cast<int>(sizeof(buf))); - out << "subject = " << buf << '\n'; - out << "depth = " << X509_STORE_CTX_get_error_depth(c) << '\n'; - out << "error = " << X509_verify_cert_error_string(err) << '\n'; - out << IceInternal::fdToString(SSL_get_fd(ssl)); - } - return ok; -} - -#ifndef OPENSSL_NO_DH -DH* -IceSSL::SharedInstance::dhParams(int keyLength) -{ - return _dhParams->get(keyLength); -} -#endif - -int -IceSSL::SharedInstance::parseProtocols(const StringSeq& protocols) -{ - int v = 0; - - for(Ice::StringSeq::const_iterator p = protocols.begin(); p != protocols.end(); ++p) - { - string prot = *p; - - if(prot == "ssl3" || prot == "sslv3") - { - v |= SSLv3; - } - else if(prot == "tls" || prot == "tls1" || prot == "tlsv1" || prot == "tls1_0" || prot == "tlsv1_0") - { - v |= TLSv1_0; - } - else if(prot == "tls1_1" || prot == "tlsv1_1") - { - v |= TLSv1_1; - } - else if(prot == "tls1_2" || prot == "tlsv1_2") - { - v |= TLSv1_2; - } - else - { - PluginInitializationException ex(__FILE__, __LINE__); - ex.reason = "IceSSL: unrecognized protocol `" + prot + "'"; - throw ex; - } - } - - return v; -} - -SSL_METHOD* -IceSSL::SharedInstance::getMethod(int /*protocols*/) -{ - // - // Despite its name, the SSLv23 method can negotiate SSL3, TLS1.0, TLS1.1, and TLS1.2. - // We use the const_cast for backward compatibility with older OpenSSL releases. - // - SSL_METHOD* meth = const_cast<SSL_METHOD*>(SSLv23_method()); - - /* - * Early versions of OpenSSL 1.0.1 would not negotiate a TLS1.2 connection using - * the SSLv23 method. You can enable the code below to override the method. - if(protocols & TLSv1_2) - { - meth = const_cast<SSL_METHOD*>(TLSv1_2_method()); - } - */ - - return meth; -} - -void -IceSSL::SharedInstance::setOptions(int protocols) -{ - long opts = SSL_OP_NO_SSLv2; // SSLv2 is not supported. - if(!(protocols & SSLv3)) - { - opts |= SSL_OP_NO_SSLv3; - } - if(!(protocols & TLSv1_0)) - { - opts |= SSL_OP_NO_TLSv1; - } -#ifdef SSL_OP_NO_TLSv1_1 - if(!(protocols & TLSv1_1)) - { - opts |= SSL_OP_NO_TLSv1_1; - // - // The value of SSL_OP_NO_TLSv1_1 changed between 1.0.1a and 1.0.1b. - // - if(SSL_OP_NO_TLSv1_1 == 0x00000400L) - { - opts |= 0x10000000L; // New value of SSL_OP_NO_TLSv1_1. - } - } -#endif -#ifdef SSL_OP_NO_TLSv1_2 - if(!(protocols & TLSv1_2)) - { - opts |= SSL_OP_NO_TLSv1_2; - } -#endif - SSL_CTX_set_options(_ctx, opts); -} - -IceSSL::Instance::Instance(const SharedInstancePtr& sharedInstance, Ice::Short type, const std::string& protocol) : - ProtocolInstance(sharedInstance->communicator(), type, protocol), - _sharedInstance(sharedInstance) +IceSSL::Instance::Instance(const SSLEnginePtr& engine, Short type, const string& protocol) : + ProtocolInstance(engine->communicator(), type, protocol), + _engine(engine) { _securityTraceLevel = properties()->getPropertyAsInt("IceSSL.Trace.Security"); _securityTraceCategory = "Security"; @@ -1157,28 +32,10 @@ IceSSL::Instance::~Instance() { } -void -IceSSL::Instance::traceConnection(SSL* ssl, bool incoming) +bool +IceSSL::Instance::initialized() const { - Trace out(_logger, _securityTraceCategory); - out << "SSL summary for " << (incoming ? "incoming" : "outgoing") << " connection\n"; - - // - // The const_cast is necesary because Solaris still uses OpenSSL 0.9.7. - // - //const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); - SSL_CIPHER *cipher = const_cast<SSL_CIPHER*>(SSL_get_current_cipher(ssl)); - if(!cipher) - { - out << "unknown cipher\n"; - } - else - { - out << "cipher = " << SSL_CIPHER_get_name(cipher) << "\n"; - out << "bits = " << SSL_CIPHER_get_bits(cipher, 0) << "\n"; - out << "protocol = " << SSL_get_version(ssl) << "\n"; - } - out << IceInternal::fdToString(SSL_get_fd(ssl)); + return _engine->initialized(); } int @@ -1192,4 +49,3 @@ IceSSL::Instance::securityTraceCategory() const { return _securityTraceCategory; } - diff --git a/cpp/src/IceSSL/Instance.h b/cpp/src/IceSSL/Instance.h index d549b60f179..ee295a6d7d1 100644 --- a/cpp/src/IceSSL/Instance.h +++ b/cpp/src/IceSSL/Instance.h @@ -12,114 +12,37 @@ #include <IceSSL/InstanceF.h> #include <IceSSL/UtilF.h> -#include <Ice/CommunicatorF.h> -#include <Ice/Network.h> #include <Ice/ProtocolInstance.h> #include <Ice/ProtocolPluginFacadeF.h> #include <IceSSL/Plugin.h> +#include <IceSSL/SSLEngine.h> #include <IceSSL/TrustManagerF.h> #include <Ice/BuiltinSequences.h> namespace IceSSL { -class SharedInstance : public IceUtil::Shared -{ -public: - - SharedInstance(const Ice::CommunicatorPtr&); - ~SharedInstance(); - - void initialize(); - void context(SSL_CTX*); - SSL_CTX* context() const; - void setCertificateVerifier(const CertificateVerifierPtr&); - void setPasswordPrompt(const PasswordPromptPtr&); - - Ice::CommunicatorPtr communicator() const; - - void verifyPeer(SSL*, SOCKET, const std::string&, const NativeConnectionInfoPtr&); - - std::string sslErrors() const; - - void destroy(); - - // - // OpenSSL callbacks. - // - std::string password(bool); - int verifyCallback(int, SSL*, X509_STORE_CTX*); -#ifndef OPENSSL_NO_DH - DH* dhParams(int); -#endif - -private: - - enum Protocols { SSLv3 = 0x01, TLSv1_0 = 0x02, TLSv1_1 = 0x04, TLSv1_2 = 0x08 }; - static int parseProtocols(const Ice::StringSeq&); - - static SSL_METHOD* getMethod(int); - - void setOptions(int); - - bool _initOpenSSL; - const Ice::CommunicatorPtr _communicator; - const Ice::LoggerPtr _logger; - int _securityTraceLevel; - std::string _securityTraceCategory; - bool _initialized; - SSL_CTX* _ctx; - std::string _defaultDir; - bool _checkCertName; - int _verifyDepthMax; - int _verifyPeer; - std::string _password; -#ifndef OPENSSL_NO_DH - DHParamsPtr _dhParams; -#endif - CertificateVerifierPtr _verifier; - PasswordPromptPtr _prompt; - TrustManagerPtr _trustManager; -}; - class Instance : public IceInternal::ProtocolInstance { public: - Instance(const SharedInstancePtr&, Ice::Short, const std::string&); + Instance(const SSLEnginePtr&, Ice::Short, const std::string&); virtual ~Instance(); - SSL_CTX* - context() const - { - return _sharedInstance->context(); - } - - std::string - sslErrors() const - { - return _sharedInstance->sslErrors(); - } - - SharedInstancePtr - sharedInstance() const - { - return _sharedInstance; - } - - void - verifyPeer(SSL* ssl, SOCKET fd, const std::string& host, const NativeConnectionInfoPtr& info) + SSLEnginePtr + engine() const { - _sharedInstance->verifyPeer(ssl, fd, host, info); + return _engine; } + + bool initialized() const; - void traceConnection(SSL*, bool); int securityTraceLevel() const; std::string securityTraceCategory() const; private: - const SharedInstancePtr _sharedInstance; + const SSLEnginePtr _engine; int _securityTraceLevel; std::string _securityTraceCategory; }; diff --git a/cpp/src/IceSSL/InstanceF.h b/cpp/src/IceSSL/InstanceF.h index 3c2bcac856b..6c8f7f33d92 100644 --- a/cpp/src/IceSSL/InstanceF.h +++ b/cpp/src/IceSSL/InstanceF.h @@ -21,10 +21,6 @@ class Instance; IceUtil::Shared* upCast(IceSSL::Instance*); typedef IceInternal::Handle<Instance> InstancePtr; -class SharedInstance; -IceUtil::Shared* upCast(IceSSL::SharedInstance*); -typedef IceInternal::Handle<SharedInstance> SharedInstancePtr; - } #endif diff --git a/cpp/src/IceSSL/Makefile b/cpp/src/IceSSL/Makefile index adfe54a84db..0b77453d20e 100644 --- a/cpp/src/IceSSL/Makefile +++ b/cpp/src/IceSSL/Makefile @@ -21,13 +21,17 @@ OBJS = AcceptorI.o \ ConnectorI.o \ EndpointInfo.o \ ConnectionInfo.o \ - EndpointI.o \ - Instance.o \ - PluginI.o \ - TransceiverI.o \ - Util.o \ + EndpointI.o \ + Instance.o \ + PluginI.o \ + TransceiverI.o \ + SecureTransportTransceiverI.o \ + Util.o \ RFC2253.o \ - TrustManager.o + TrustManager.o \ + SSLEngine.o \ + OpenSSLEngine.o \ + SecureTransportEngine.o SRCS = $(OBJS:.o=.cpp) @@ -42,7 +46,7 @@ include $(top_srcdir)/config/Make.rules CPPFLAGS := -I.. $(CPPFLAGS) -DICE_SSL_API_EXPORTS $(OPENSSL_FLAGS) SLICE2CPPFLAGS := --ice --include-dir IceSSL --dll-export ICE_SSL_API $(SLICE2CPPFLAGS) -LINKWITH := $(BZIP2_RPATH_LINK) -lIce -lIceUtil $(OPENSSL_LIBS) $(CXXLIBS) +LINKWITH := $(BZIP2_RPATH_LINK) -lIce -lIceUtil $(SSL_OS_LIBS) $(CXXLIBS) ifeq ($(STATICLIBS),yes) $(libdir)/$(LIBNAME): $(OBJS) diff --git a/cpp/src/IceSSL/Makefile.mak b/cpp/src/IceSSL/Makefile.mak index 5dc4328322a..33ea5d194b5 100644 --- a/cpp/src/IceSSL/Makefile.mak +++ b/cpp/src/IceSSL/Makefile.mak @@ -25,7 +25,9 @@ OBJS = AcceptorI.obj \ TransceiverI.obj \ Util.obj \ RFC2253.obj \ - TrustManager.obj + TrustManager.obj \ + SSLEngine.obj \ + OpenSSLEngine.obj SRCS = $(OBJS:.obj=.cpp) diff --git a/cpp/src/IceSSL/OpenSSLEngine.cpp b/cpp/src/IceSSL/OpenSSLEngine.cpp new file mode 100644 index 00000000000..c73e27da33a --- /dev/null +++ b/cpp/src/IceSSL/OpenSSLEngine.cpp @@ -0,0 +1,920 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2014 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/Config.h> + +#include <IceSSL/SSLEngine.h> +#include <IceSSL/Util.h> +#include <IceSSL/TrustManager.h> + +#include <Ice/Communicator.h> +#include <Ice/LocalException.h> +#include <Ice/Logger.h> +#include <Ice/LoggerUtil.h> +#include <Ice/Properties.h> + +#include <IceUtil/StringConverter.h> +#include <IceUtil/StringUtil.h> +#include <IceUtil/Mutex.h> +#include <IceUtil/MutexPtrLock.h> +#include <IceUtil/FileUtil.h> + +#ifdef ICE_USE_OPENSSL + +#include <openssl/rand.h> +#include <openssl/err.h> +#include <openssl/ssl.h> + +using namespace std; +using namespace Ice; +using namespace IceSSL; + +namespace +{ + +IceUtil::Mutex* staticMutex = 0; +int instanceCount = 0; +IceUtil::Mutex* locks = 0; + +class Init +{ +public: + + Init() + { + staticMutex = new IceUtil::Mutex; + } + + ~Init() + { + delete staticMutex; + staticMutex = 0; + + if(locks) + { + delete[] locks; + locks = 0; + } + } +}; + +Init init; +} + +extern "C" +{ + +// +// OpenSSL mutex callback. +// +void +IceSSL_opensslLockCallback(int mode, int n, const char* /*file*/, int /*line*/) +{ + assert(locks); + if(mode & CRYPTO_LOCK) + { + locks[n].lock(); + } + else + { + locks[n].unlock(); + } +} + +// +// OpenSSL thread id callback. +// +unsigned long +IceSSL_opensslThreadIdCallback() +{ +# if defined(_WIN32) + return static_cast<unsigned long>(GetCurrentThreadId()); +# elif defined(__FreeBSD__) || defined(__APPLE__) || defined(__osf1__) + // + // On some platforms, pthread_t is a pointer to a per-thread structure. + // + return reinterpret_cast<unsigned long>(pthread_self()); +# elif (defined(__linux) || defined(__sun) || defined(__hpux)) || defined(_AIX) + // + // On Linux, Solaris, HP-UX and AIX, pthread_t is an integer. + // + return static_cast<unsigned long>(pthread_self()); +# else +# error "Unknown platform" +# endif +} + +int +IceSSL_opensslPasswordCallback(char* buf, int size, int flag, void* userData) +{ + OpenSSLEngine* p = reinterpret_cast<OpenSSLEngine*>(userData); + string passwd = p->password(flag == 1); + int sz = static_cast<int>(passwd.size()); + if(sz > size) + { + sz = size - 1; + } +# if defined(_WIN32) + strncpy_s(buf, size, passwd.c_str(), sz); +# else + strncpy(buf, passwd.c_str(), sz); +# endif + buf[sz] = '\0'; + + for(string::iterator i = passwd.begin(); i != passwd.end(); ++i) + { + *i = '\0'; + } + + return sz; +} + +# ifndef OPENSSL_NO_DH +DH* +IceSSL_opensslDHCallback(SSL* ssl, int /*isExport*/, int keyLength) +{ + OpenSSLEngine* p = reinterpret_cast<OpenSSLEngine*>(SSL_CTX_get_ex_data(ssl->ctx, 0)); + return p->dhParams(keyLength); +} +# endif + +int +IceSSL_opensslVerifyCallback(int ok, X509_STORE_CTX* ctx) +{ + SSL* ssl = reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); + OpenSSLEngine* p = reinterpret_cast<OpenSSLEngine*>(SSL_CTX_get_ex_data(ssl->ctx, 0)); + return p->verifyCallback(ok, ssl, ctx); +} + +} + +namespace +{ +bool +passwordError() +{ + int reason = ERR_GET_REASON(ERR_peek_error()); + return (reason == PEM_R_BAD_BASE64_DECODE || + reason == PEM_R_BAD_DECRYPT || + reason == PEM_R_BAD_PASSWORD_READ || + reason == PEM_R_PROBLEMS_GETTING_PASSWORD); +} + +} + +IceUtil::Shared* IceSSL::upCast(IceSSL::OpenSSLEngine* p) { return p; } + +OpenSSLEngine::OpenSSLEngine(const CommunicatorPtr& communicator) : + SSLEngine(communicator), + _initialized(false), + _ctx(0) +{ + __setNoDelete(true); + + // + // Initialize OpenSSL if necessary. + // + IceUtilInternal::MutexPtrLock<IceUtil::Mutex> sync(staticMutex); + instanceCount++; + + if(instanceCount == 1) + { + PropertiesPtr properties = communicator->getProperties(); + + // + // The IceSSL.InitOpenSSL property specifies whether we should perform the global + // startup (and shutdown) tasks for the OpenSSL library. + // + // If an application uses multiple components that each depend on OpenSSL, the + // application should disable OpenSSL initialization in those components and + // perform the initialization itself. + // + _initOpenSSL = properties->getPropertyAsIntWithDefault("IceSSL.InitOpenSSL", 1) > 0; + if(_initOpenSSL) + { + // + // Create the mutexes and set the callbacks. + // + if(!locks) + { + locks = new IceUtil::Mutex[CRYPTO_num_locks()]; + CRYPTO_set_locking_callback(IceSSL_opensslLockCallback); + CRYPTO_set_id_callback(IceSSL_opensslThreadIdCallback); + } + + // + // Load human-readable error messages. + // + SSL_load_error_strings(); + + // + // Initialize the SSL library. + // + SSL_library_init(); + + // + // This is necessary to allow programs that use OpenSSL 0.9.x to + // load private key files generated by OpenSSL 1.x. + // + OpenSSL_add_all_algorithms(); + + // + // Initialize the PRNG. + // +# ifdef WINDOWS + RAND_screen(); // Uses data from the screen if possible. +# endif + char randFile[1024]; + if(RAND_file_name(randFile, sizeof(randFile))) // Gets the name of a default seed file. + { + RAND_load_file(randFile, 1024); + } + + string randFiles = properties->getProperty("IceSSL.Random"); + + if(!randFiles.empty()) + { + vector<string> files; + string defaultDir = properties->getProperty("IceSSL.DefaultDir"); + + if(!IceUtilInternal::splitString(randFiles, IceUtilInternal::pathsep, files)) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: invalid value for IceSSL.Random:\n" + randFiles; + throw ex; + } + for(vector<string>::iterator p = files.begin(); p != files.end(); ++p) + { + string file = *p; + if(!checkPath(file, defaultDir, false)) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: entropy data file not found:\n" + file; + throw ex; + } + if(!RAND_load_file(file.c_str(), 1024)) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: unable to load entropy data from " + file; + throw ex; + } + } + } +# ifndef _WIN32 + // + // The Entropy Gathering Daemon (EGD) is not available on Windows. + // The file should be a Unix domain socket for the daemon. + // + string entropyDaemon = properties->getProperty("IceSSL.EntropyDaemon"); + if(!entropyDaemon.empty()) + { + if(RAND_egd(entropyDaemon.c_str()) <= 0) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: EGD failure using file " + entropyDaemon; + throw ex; + } + } +# endif + if(!RAND_status()) + { + getLogger()->warning("IceSSL: insufficient data to initialize PRNG"); + } + } + else + { + if(!properties->getProperty("IceSSL.Random").empty()) + { + getLogger()->warning("IceSSL: ignoring IceSSL.Random because OpenSSL initialization is disabled"); + } +# ifndef _WIN32 + else if(!properties->getProperty("IceSSL.EntropyDaemon").empty()) + { + getLogger()->warning("IceSSL: ignoring IceSSL.EntropyDaemon because OpenSSL initialization is disabled"); + } +# endif + } + } + __setNoDelete(false); +} + +OpenSSLEngine::~OpenSSLEngine() +{ + // + // Clean up OpenSSL resources. + // + IceUtilInternal::MutexPtrLock<IceUtil::Mutex> sync(staticMutex); + + if(--instanceCount == 0 && _initOpenSSL) + { + // + // NOTE: We can't destroy the locks here: threads which might have called openssl methods + // might access openssl locks upon termination (from DllMain/THREAD_DETACHED). Instead, + // we release the locks in the ~Init() static destructor. See bug #4156. + // + //CRYPTO_set_locking_callback(0); + //CRYPTO_set_id_callback(0); + //delete[] locks; + //locks = 0; + + CRYPTO_cleanup_all_ex_data(); + RAND_cleanup(); + ERR_free_strings(); + EVP_cleanup(); + } +} + +bool +OpenSSLEngine::initialized() const +{ + IceUtil::Mutex::Lock lock(_mutex); + return _initialized; +} + +void +OpenSSLEngine::initialize() +{ + IceUtil::Mutex::Lock lock(_mutex); + if(_initialized) + { + return; + } + + try + { + SSLEngine::initialize(); + + const string propPrefix = "IceSSL."; + PropertiesPtr properties = communicator()->getProperties(); + + // + // Protocols selects which protocols to enable. + // + const int protocols = parseProtocols(properties->getPropertyAsList(propPrefix + "Protocols")); + + // + // Create an SSL context if the application hasn't supplied one. + // + if(!_ctx) + { + _ctx = SSL_CTX_new(getMethod(protocols)); + if(!_ctx) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: unable to create SSL context:\n" + sslErrors(); + throw ex; + } + + // + // Check for a default directory. We look in this directory for + // files mentioned in the configuration. + // + string defaultDir = properties->getProperty(propPrefix + "DefaultDir"); + + // + // If the configuration defines a password, or the application has supplied + // a password prompt object, then register a password callback. Otherwise, + // let OpenSSL use its default behavior. + // + { + // TODO: Support quoted value? + string password = properties->getProperty(propPrefix + "Password"); + if(!password.empty() || getPasswordPrompt()) + { + SSL_CTX_set_default_passwd_cb(_ctx, IceSSL_opensslPasswordCallback); + SSL_CTX_set_default_passwd_cb_userdata(_ctx, this); + setPassword(password); + } + } + + int passwordRetryMax = properties->getPropertyAsIntWithDefault(propPrefix + "PasswordRetryMax", 3); + + // + // Establish the location of CA certificates. + // + { + string caFile = properties->getProperty(propPrefix + "CertAuthFile"); + string caDir = properties->getPropertyWithDefault(propPrefix + "CertAuthDir", defaultDir); + const char* file = 0; + const char* dir = 0; + if(!caFile.empty()) + { + if(!checkPath(caFile, defaultDir, false)) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: CA certificate file not found:\n" + caFile; + throw ex; + } + file = caFile.c_str(); + } + if(!caDir.empty()) + { + if(!checkPath(caDir, defaultDir, true)) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: CA certificate directory not found:\n" + caDir; + throw ex; + } + dir = caDir.c_str(); + } + if(file || dir) + { + // + // The certificate may be stored in an encrypted file, so handle + // password retries. + // + int count = 0; + int err = 0; + while(count < passwordRetryMax) + { + ERR_clear_error(); + err = SSL_CTX_load_verify_locations(_ctx, file, dir); + if(err) + { + break; + } + ++count; + } + if(err == 0) + { + string msg = "IceSSL: unable to establish CA certificates"; + if(passwordError()) + { + msg += ":\ninvalid password"; + } + else + { + string err = sslErrors(); + if(!err.empty()) + { + msg += ":\n" + err; + } + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + } + } + + // + // Establish the certificate chains and private keys. One RSA certificate and + // one DSA certificate are allowed. + // + { + string certFile = properties->getProperty(propPrefix + "CertFile"); + string keyFile = properties->getProperty(propPrefix + "KeyFile"); + vector<string>::size_type numCerts = 0; + if(!certFile.empty()) + { + vector<string> files; + if(!IceUtilInternal::splitString(certFile, IceUtilInternal::pathsep, files) || files.size() > 2) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: invalid value for " + propPrefix + "CertFile:\n" + certFile; + throw ex; + } + numCerts = files.size(); + for(vector<string>::iterator p = files.begin(); p != files.end(); ++p) + { + string file = *p; + if(!checkPath(file, defaultDir, false)) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: certificate file not found:\n" + file; + throw ex; + } + // + // The certificate may be stored in an encrypted file, so handle + // password retries. + // + int count = 0; + int err = 0; + while(count < passwordRetryMax) + { + ERR_clear_error(); + err = SSL_CTX_use_certificate_chain_file(_ctx, file.c_str()); + if(err) + { + break; + } + ++count; + } + if(err == 0) + { + string msg = "IceSSL: unable to load certificate chain from file " + file; + if(passwordError()) + { + msg += ":\ninvalid password"; + } + else + { + string err = sslErrors(); + if(!err.empty()) + { + msg += ":\n" + err; + } + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + } + } + if(keyFile.empty()) + { + keyFile = certFile; // Assume the certificate file also contains the private key. + } + if(!keyFile.empty()) + { + vector<string> files; + if(!IceUtilInternal::splitString(keyFile, IceUtilInternal::pathsep, files) || files.size() > 2) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: invalid value for " + propPrefix + "KeyFile:\n" + keyFile; + throw ex; + } + if(files.size() != numCerts) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: " + propPrefix + "KeyFile does not agree with " + propPrefix + "CertFile"; + throw ex; + } + for(vector<string>::iterator p = files.begin(); p != files.end(); ++p) + { + string file = *p; + if(!checkPath(file, defaultDir, false)) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: key file not found:\n" + file; + throw ex; + } + // + // The private key may be stored in an encrypted file, so handle + // password retries. + // + int count = 0; + int err = 0; + while(count < passwordRetryMax) + { + ERR_clear_error(); + err = SSL_CTX_use_PrivateKey_file(_ctx, file.c_str(), SSL_FILETYPE_PEM); + if(err) + { + break; + } + ++count; + } + if(err == 0) + { + string msg = "IceSSL: unable to load private key from file " + file; + if(passwordError()) + { + msg += ":\ninvalid password"; + } + else + { + string err = sslErrors(); + if(!err.empty()) + { + msg += ":\n" + err; + } + } + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + } + if(!SSL_CTX_check_private_key(_ctx)) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: unable to validate private key(s):\n" + sslErrors(); + throw ex; + } + } + } + + // + // Diffie Hellman configuration. + // + { +# ifndef OPENSSL_NO_DH + _dhParams = new DHParams; + SSL_CTX_set_options(_ctx, SSL_OP_SINGLE_DH_USE); + SSL_CTX_set_tmp_dh_callback(_ctx, IceSSL_opensslDHCallback); +# endif + // + // Properties have the following form: + // + // ...DH.<keyLength>=file + // + const string dhPrefix = propPrefix + "DH."; + PropertyDict d = properties->getPropertiesForPrefix(dhPrefix); + if(!d.empty()) + { +# ifdef OPENSSL_NO_DH + getLogger()->warning("IceSSL: OpenSSL is not configured for Diffie Hellman"); +# else + for(PropertyDict::iterator p = d.begin(); p != d.end(); ++p) + { + string s = p->first.substr(dhPrefix.size()); + int keyLength = atoi(s.c_str()); + if(keyLength > 0) + { + string file = p->second; + if(!checkPath(file, defaultDir, false)) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: DH parameter file not found:\n" + file; + throw ex; + } + if(!_dhParams->add(keyLength, file)) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: unable to read DH parameter file " + file; + throw ex; + } + } + } +# endif + } + } + } + + // + // Store a pointer to ourself for use in OpenSSL callbacks. + // + SSL_CTX_set_ex_data(_ctx, 0, this); + + // + // This is necessary for successful interop with Java. Without it, a Java + // client would fail to reestablish a connection: the server gets the + // error "session id context uninitialized" and the client receives + // "SSLHandshakeException: Remote host closed connection during handshake". + // + SSL_CTX_set_session_cache_mode(_ctx, SSL_SESS_CACHE_OFF); + + // + // Although we disable session caching, we still need to set a session ID + // context (ICE-5103). The value can be anything; here we just use the + // pointer to this SharedInstance object. + // + SSL_CTX_set_session_id_context(_ctx, reinterpret_cast<unsigned char*>(this), + static_cast<unsigned int>(sizeof(this))); + + // + // Select protocols. + // + if(protocols != 0) + { + setOptions(protocols); + } + + // + // Establish the cipher list. + // + string ciphers = properties->getProperty(propPrefix + "Ciphers"); + if(!ciphers.empty()) + { + if(!SSL_CTX_set_cipher_list(_ctx, ciphers.c_str())) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: unable to set ciphers using `" + ciphers + "':\n" + sslErrors(); + throw ex; + } + } + + // + // Determine whether a certificate is required from the peer. + // + { + int sslVerifyMode; + switch(getVerifyPeer()) + { + case 0: + sslVerifyMode = SSL_VERIFY_NONE; + break; + case 1: + sslVerifyMode = SSL_VERIFY_PEER; + break; + case 2: + sslVerifyMode = SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + break; + default: + { + assert(false); + } + } + SSL_CTX_set_verify(_ctx, sslVerifyMode, IceSSL_opensslVerifyCallback); + } + } + catch(...) + { + // + // We free the SSL context regardless of whether the plugin created it + // or the application supplied it. + // + SSL_CTX_free(_ctx); + _ctx = 0; + throw; + } + + _initialized = true; +} + +void +OpenSSLEngine::context(ContextRef context) +{ + if(initialized()) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: plug-in is already initialized"; + throw ex; + } + + assert(!_ctx); + _ctx = context; +} + +ContextRef +OpenSSLEngine::context() const +{ + return _ctx; +} + +void +OpenSSLEngine::verifyPeer(SSL* ssl, SOCKET fd, const string& address, const NativeConnectionInfoPtr& info) +{ + long result = SSL_get_verify_result(ssl); + if(result != X509_V_OK) + { + if(getVerifyPeer() == 0) + { + if(securityTraceLevel() >= 1) + { + ostringstream ostr; + ostr << "IceSSL: ignoring certificate verification failure:\n" << X509_verify_cert_error_string(result); + getLogger()->trace(securityTraceCategory(), ostr.str()); + } + } + else + { + ostringstream ostr; + ostr << "IceSSL: certificate verification failed:\n" << X509_verify_cert_error_string(result); + string msg = ostr.str(); + if(securityTraceLevel() >= 1) + { + getLogger()->trace(securityTraceCategory(), msg); + } + SecurityException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + } + SSLEngine::verifyPeer(fd, address, info); +} + +string +OpenSSLEngine::sslErrors() const +{ + return getSslErrors(securityTraceLevel() >= 1); +} + +void +OpenSSLEngine::destroy() +{ + if(_ctx) + { + SSL_CTX_free(_ctx); + } +} + +int +OpenSSLEngine::verifyCallback(int ok, SSL* ssl, X509_STORE_CTX* c) +{ + if(!ok && securityTraceLevel() >= 1) + { + X509* cert = X509_STORE_CTX_get_current_cert(c); + int err = X509_STORE_CTX_get_error(c); + char buf[256]; + + Trace out(getLogger(), securityTraceCategory()); + out << "certificate verification failure\n"; + + X509_NAME_oneline(X509_get_issuer_name(cert), buf, static_cast<int>(sizeof(buf))); + out << "issuer = " << buf << '\n'; + X509_NAME_oneline(X509_get_subject_name(cert), buf, static_cast<int>(sizeof(buf))); + out << "subject = " << buf << '\n'; + out << "depth = " << X509_STORE_CTX_get_error_depth(c) << '\n'; + out << "error = " << X509_verify_cert_error_string(err) << '\n'; + out << IceInternal::fdToString(SSL_get_fd(ssl)); + } + return ok; +} + +# ifndef OPENSSL_NO_DH +DH* +OpenSSLEngine::dhParams(int keyLength) +{ + return _dhParams->get(keyLength); +} +# endif + +int +OpenSSLEngine::parseProtocols(const StringSeq& protocols) const +{ + int v = 0; + + for(Ice::StringSeq::const_iterator p = protocols.begin(); p != protocols.end(); ++p) + { + string prot = *p; + + if(prot == "ssl3" || prot == "sslv3") + { + v |= SSLv3; + } + else if(prot == "tls" || prot == "tls1" || prot == "tlsv1" || prot == "tls1_0" || prot == "tlsv1_0") + { + v |= TLSv1_0; + } + else if(prot == "tls1_1" || prot == "tlsv1_1") + { + v |= TLSv1_1; + } + else if(prot == "tls1_2" || prot == "tlsv1_2") + { + v |= TLSv1_2; + } + else + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: unrecognized protocol `" + prot + "'"; + throw ex; + } + } + + return v; +} + +SSL_METHOD* +OpenSSLEngine::getMethod(int /*protocols*/) +{ + // + // Despite its name, the SSLv23 method can negotiate SSL3, TLS1.0, TLS1.1, and TLS1.2. + // We use the const_cast for backward compatibility with older OpenSSL releases. + // + SSL_METHOD* meth = const_cast<SSL_METHOD*>(SSLv23_method()); + + /* + * Early versions of OpenSSL 1.0.1 would not negotiate a TLS1.2 connection using + * the SSLv23 method. You can enable the code below to override the method. + if(protocols & TLSv1_2) + { + meth = const_cast<SSL_METHOD*>(TLSv1_2_method()); + } + */ + + return meth; +} + +void +OpenSSLEngine::setOptions(int protocols) +{ + long opts = SSL_OP_NO_SSLv2; // SSLv2 is not supported. + if(!(protocols & SSLv3)) + { + opts |= SSL_OP_NO_SSLv3; + } + if(!(protocols & TLSv1_0)) + { + opts |= SSL_OP_NO_TLSv1; + } +# ifdef SSL_OP_NO_TLSv1_1 + if(!(protocols & TLSv1_1)) + { + opts |= SSL_OP_NO_TLSv1_1; + // + // The value of SSL_OP_NO_TLSv1_1 changed between 1.0.1a and 1.0.1b. + // + if(SSL_OP_NO_TLSv1_1 == 0x00000400L) + { + opts |= 0x10000000L; // New value of SSL_OP_NO_TLSv1_1. + } + } +# endif +# ifdef SSL_OP_NO_TLSv1_2 + if(!(protocols & TLSv1_2)) + { + opts |= SSL_OP_NO_TLSv1_2; + } +# endif + SSL_CTX_set_options(_ctx, opts); +} + +#endif // ICESSL_USE_OPENSSL diff --git a/cpp/src/IceSSL/PluginI.cpp b/cpp/src/IceSSL/PluginI.cpp index 91e07455ff8..02f9cf1a6e1 100644 --- a/cpp/src/IceSSL/PluginI.cpp +++ b/cpp/src/IceSSL/PluginI.cpp @@ -30,8 +30,7 @@ extern "C" ICE_DECLSPEC_EXPORT Ice::Plugin* createIceSSL(const CommunicatorPtr& communicator, const string& /*name*/, const StringSeq& /*args*/) { - PluginI* plugin = new PluginI(communicator); - return plugin; + return new PluginI(communicator); } } @@ -41,8 +40,12 @@ createIceSSL(const CommunicatorPtr& communicator, const string& /*name*/, const // IceSSL::PluginI::PluginI(const Ice::CommunicatorPtr& communicator) { - _sharedInstance = new SharedInstance(communicator); - +#ifdef ICE_USE_OPENSSL + _engine = new OpenSSLEngine(communicator); +#else + _engine = new SecureTransportEngine(communicator); +#endif + IceInternal::ProtocolPluginFacadePtr facade = IceInternal::getProtocolPluginFacade(communicator); // @@ -50,42 +53,44 @@ IceSSL::PluginI::PluginI(const Ice::CommunicatorPtr& communicator) // in initialize, because the communicator may need to interpret // proxies before the plug-in is fully initialized. // - facade->addEndpointFactory(new EndpointFactoryI(new Instance(_sharedInstance, EndpointType, "ssl"))); + facade->addEndpointFactory(new EndpointFactoryI(new Instance(_engine, EndpointType, "ssl"))); } void IceSSL::PluginI::initialize() { - _sharedInstance->initialize(); + _engine->initialize(); } void IceSSL::PluginI::destroy() { - _sharedInstance->destroy(); - _sharedInstance = 0; + _engine->destroy(); + _engine = 0; } void -IceSSL::PluginI::setContext(SSL_CTX* context) +IceSSL::PluginI::setCertificateVerifier(const CertificateVerifierPtr& verifier) { - _sharedInstance->context(context); + _engine->setCertificateVerifier(verifier); } -SSL_CTX* -IceSSL::PluginI::getContext() +void +IceSSL::PluginI::setPasswordPrompt(const PasswordPromptPtr& prompt) { - return _sharedInstance->context(); + _engine->setPasswordPrompt(prompt); } +#ifdef ICE_USE_OPENSSL void -IceSSL::PluginI::setCertificateVerifier(const CertificateVerifierPtr& verifier) +IceSSL::PluginI::setContext(ContextRef context) { - _sharedInstance->setCertificateVerifier(verifier); + _engine->context(context); } -void -IceSSL::PluginI::setPasswordPrompt(const PasswordPromptPtr& prompt) +ContextRef +IceSSL::PluginI::getContext() { - _sharedInstance->setPasswordPrompt(prompt); + return _engine->context(); } +#endif diff --git a/cpp/src/IceSSL/PluginI.h b/cpp/src/IceSSL/PluginI.h index 4a0a030987b..7d1903aa7bd 100644 --- a/cpp/src/IceSSL/PluginI.h +++ b/cpp/src/IceSSL/PluginI.h @@ -11,13 +11,18 @@ #define ICE_SSL_PLUGIN_I_H #include <IceSSL/Plugin.h> -#include <IceSSL/InstanceF.h> +#include <IceSSL/SSLEngineF.h> #include <Ice/CommunicatorF.h> namespace IceSSL { -class PluginI : public IceSSL::Plugin +class PluginI : +#ifdef ICE_USE_OPENSSL + public OpenSSLPlugin +#else + public IceSSL::Plugin +#endif { public: @@ -32,14 +37,21 @@ public: // // From IceSSL::Plugin. // - virtual void setContext(SSL_CTX*); - virtual SSL_CTX* getContext(); virtual void setCertificateVerifier(const CertificateVerifierPtr&); virtual void setPasswordPrompt(const PasswordPromptPtr&); +#ifdef ICE_USE_OPENSSL + virtual void setContext(ContextRef); + virtual ContextRef getContext(); +#endif + private: - SharedInstancePtr _sharedInstance; +#ifdef ICE_USE_OPENSSL + OpenSSLEnginePtr _engine; +#else + SecureTransportEnginePtr _engine; +#endif }; } diff --git a/cpp/src/IceSSL/RFC2253.cpp b/cpp/src/IceSSL/RFC2253.cpp index 331838bf928..eaa86e979c5 100644 --- a/cpp/src/IceSSL/RFC2253.cpp +++ b/cpp/src/IceSSL/RFC2253.cpp @@ -231,6 +231,7 @@ parseAttributeTypeAndValue(const string& data, size_t& pos) pair<string, string> p; p.first = parseAttributeType(data, pos); eatWhite(data, pos); + if(pos >= data.size()) { throw ParseException(__FILE__, __LINE__, "invalid attribute type/value pair (unexpected end of data)"); diff --git a/cpp/src/IceSSL/SSLEngine.cpp b/cpp/src/IceSSL/SSLEngine.cpp new file mode 100644 index 00000000000..adbfb439bc0 --- /dev/null +++ b/cpp/src/IceSSL/SSLEngine.cpp @@ -0,0 +1,291 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2014 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/SSLEngine.h> +#include <IceSSL/TrustManager.h> + +#include <IceUtil/StringUtil.h> + +#include <Ice/Communicator.h> +#include <Ice/Properties.h> +#include <Ice/Logger.h> +#include <Ice/LoggerUtil.h> +#include <Ice/LocalException.h> + +#include <string> + +using namespace std; +using namespace Ice; +using namespace IceSSL; + +IceUtil::Shared* IceSSL::upCast(IceSSL::SSLEngine* p) { return p; } + +IceSSL::SSLEngine::SSLEngine(const Ice::CommunicatorPtr& communicator) : + _communicator(communicator), + _logger(communicator->getLogger()), + _trustManager(new TrustManager(communicator)) +{ +} + +IceSSL::CertificateVerifierPtr +IceSSL::SSLEngine::getCertificateVerifier() const +{ + return _verifier; +} + +void +IceSSL::SSLEngine::setCertificateVerifier(const IceSSL::CertificateVerifierPtr& verifier) +{ + _verifier = verifier; +} + +IceSSL::PasswordPromptPtr +IceSSL::SSLEngine::getPasswordPrompt() const +{ + return _prompt; +} + +void +IceSSL::SSLEngine::setPasswordPrompt(const IceSSL::PasswordPromptPtr& prompt) +{ + _prompt = prompt; +} + +string +IceSSL::SSLEngine::password(bool /*encrypting*/) +{ + if(_prompt) + { + try + { + return _prompt->getPassword(); + } + catch(...) + { + // + // Don't allow exceptions to cross an OpenSSL boundary. + // + return string(); + } + } + else + { + return _password; + } +} + +string +IceSSL::SSLEngine::getPassword() const +{ + return _password; +} + +void +IceSSL::SSLEngine::setPassword(const std::string& password) +{ + _password = password; +} + +void +IceSSL::SSLEngine::initialize() +{ + const string propPrefix = "IceSSL."; + const PropertiesPtr properties = communicator()->getProperties(); + + // + // CheckCertName determines whether we compare the name in a peer's + // certificate against its hostname. + // + _checkCertName = properties->getPropertyAsIntWithDefault(propPrefix + "CheckCertName", 0) > 0; + + // + // VerifyDepthMax establishes the maximum length of a peer's certificate + // chain, including the peer's certificate. A value of 0 means there is + // no maximum. + // + _verifyDepthMax = properties->getPropertyAsIntWithDefault(propPrefix + "VerifyDepthMax", 2); + + // + // VerifyPeer determines whether certificate validation failures abort a connection. + // + _verifyPeer = properties->getPropertyAsIntWithDefault(propPrefix + "VerifyPeer", 2); + + if(_verifyPeer < 0 || _verifyPeer > 2) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: invalid value for " + propPrefix + "VerifyPeer"; + throw ex; + } + + _securityTraceLevel = properties->getPropertyAsInt("IceSSL.Trace.Security"); + _securityTraceCategory = "Security"; +} + +void +IceSSL::SSLEngine::verifyPeer(SOCKET fd, const string& address, const NativeConnectionInfoPtr& info) +{ + const CertificateVerifierPtr verifier = getCertificateVerifier(); + + // + // For an outgoing connection, we compare the proxy address (if any) against + // fields in the server's certificate (if any). + // + if(!info->nativeCerts.empty() && !address.empty()) + { + const CertificatePtr cert = info->nativeCerts[0]; + // + // Extract the IP addresses and the DNS names from the subject + // alternative names. + // + vector<pair<int, string> > subjectAltNames = cert->getSubjectAlternativeNames(); + vector<string> ipAddresses; + vector<string> dnsNames; + for(vector<pair<int, string> >::const_iterator p = subjectAltNames.begin(); p != subjectAltNames.end(); ++p) + { + if(p->first == 7) + { + ipAddresses.push_back(IceUtilInternal::toLower(p->second)); + } + else if(p->first == 2) + { + dnsNames.push_back(IceUtilInternal::toLower(p->second)); + } + } + + // + // Compare the peer's address against the common name. + // + bool certNameOK = false; + string dn; + string addrLower = IceUtilInternal::toLower(address); + { + DistinguishedName d = cert->getSubjectDN(); + dn = IceUtilInternal::toLower(string(d)); + string cn = "cn=" + addrLower; + string::size_type pos = dn.find(cn); + if(pos != string::npos) + { + // + // Ensure we match the entire common name. + // + certNameOK = (pos + cn.size() == dn.size()) || (dn[pos + cn.size()] == ','); + } + } + + // + // Compare the peer's address against the dnsName and ipAddress + // values in the subject alternative name. + // + if(!certNameOK) + { + certNameOK = find(ipAddresses.begin(), ipAddresses.end(), addrLower) != ipAddresses.end(); + } + if(!certNameOK) + { + certNameOK = find(dnsNames.begin(), dnsNames.end(), addrLower) != dnsNames.end(); + } + + // + // Log a message if the name comparison fails. If CheckCertName is defined, + // we also raise an exception to abort the connection. Don't log a message if + // CheckCertName is not defined and a verifier is present. + // + if(!certNameOK && (_checkCertName || (_securityTraceLevel >= 1 && !verifier))) + { + ostringstream ostr; + ostr << "IceSSL: "; + if(!_checkCertName) + { + ostr << "ignoring "; + } + ostr << "certificate validation failure:\npeer certificate does not have `" << address + << "' as its commonName or in its subjectAltName extension"; + if(!dn.empty()) + { + ostr << "\nSubject DN: " << dn; + } + if(!dnsNames.empty()) + { + ostr << "\nDNS names found in certificate: "; + for(vector<string>::const_iterator p = dnsNames.begin(); p != dnsNames.end(); ++p) + { + if(p != dnsNames.begin()) + { + ostr << ", "; + } + ostr << *p; + } + } + if(!ipAddresses.empty()) + { + ostr << "\nIP addresses found in certificate: "; + for(vector<string>::const_iterator p = ipAddresses.begin(); p != ipAddresses.end(); ++p) + { + if(p != ipAddresses.begin()) + { + ostr << ", "; + } + ostr << *p; + } + } + string msg = ostr.str(); + if(_securityTraceLevel >= 1) + { + Trace out(getLogger(), _securityTraceCategory); + out << msg; + } + if(_checkCertName) + { + SecurityException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + } + } + + if(_verifyDepthMax > 0 && static_cast<int>(info->certs.size()) > _verifyDepthMax) + { + ostringstream ostr; + ostr << (info->incoming ? "incoming" : "outgoing") << " connection rejected:\n" + << "length of peer's certificate chain (" << info->certs.size() << ") exceeds maximum of " + << _verifyDepthMax; + string msg = ostr.str(); + if(_securityTraceLevel >= 1) + { + getLogger()->trace(_securityTraceCategory, msg + "\n" + IceInternal::fdToString(fd)); + } + SecurityException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + + if(!_trustManager->verify(info)) + { + string msg = string(info->incoming ? "incoming" : "outgoing") + " connection rejected by trust manager"; + if(_securityTraceLevel >= 1) + { + getLogger()->trace(_securityTraceCategory, msg + "\n" + IceInternal::fdToString(fd)); + } + SecurityException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } + + if(verifier && !verifier->verify(info)) + { + string msg = string(info->incoming ? "incoming" : "outgoing") + " connection rejected by certificate verifier"; + if(_securityTraceLevel >= 1) + { + getLogger()->trace(_securityTraceCategory, msg + "\n" + IceInternal::fdToString(fd)); + } + SecurityException ex(__FILE__, __LINE__); + ex.reason = msg; + throw ex; + } +} diff --git a/cpp/src/IceSSL/SSLEngine.h b/cpp/src/IceSSL/SSLEngine.h new file mode 100644 index 00000000000..e4f1891222a --- /dev/null +++ b/cpp/src/IceSSL/SSLEngine.h @@ -0,0 +1,179 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2014 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. +// +// ********************************************************************** + +#ifndef ICE_SSL_ENGINE_H +#define ICE_SSL_ENGINE_H + +#include <IceSSL/Plugin.h> +#include <IceSSL/Util.h> +#include <IceSSL/SSLEngineF.h> +#include <IceSSL/TrustManagerF.h> + +#include <IceUtil/ScopedArray.h> +#include <IceUtil/UniquePtr.h> +#include <IceUtil/Shared.h> +#include <IceUtil/Mutex.h> +#include <Ice/CommunicatorF.h> +#include <Ice/Network.h> + +#ifdef ICE_USE_SECURE_TRANSPORT +# include <Security/Security.h> +#endif + +namespace IceSSL +{ + +class SSLEngine : public IceUtil::Shared +{ +public: + + SSLEngine(const Ice::CommunicatorPtr&); + + Ice::CommunicatorPtr communicator() const { return _communicator; } + Ice::LoggerPtr getLogger() const { return _logger; }; + + void setCertificateVerifier(const CertificateVerifierPtr&); + void setPasswordPrompt(const PasswordPromptPtr&); + std::string password(bool); + + // + // Setup the engine. + // + virtual void initialize() = 0; + + virtual bool initialized() const = 0; + + // + // Destroy the engine. + // + virtual void destroy() = 0; + + // + // Verify peer certificate + // + virtual void verifyPeer(SOCKET, const std::string&, const NativeConnectionInfoPtr&); + + + CertificateVerifierPtr getCertificateVerifier() const; + PasswordPromptPtr getPasswordPrompt() const; + + std::string getPassword() const; + void setPassword(const std::string& password); + + int getVerifyPeer() const { return _verifyPeer; } + int securityTraceLevel() const { return _securityTraceLevel; } + std::string securityTraceCategory() const { return _securityTraceCategory; } + +private: + + const Ice::CommunicatorPtr _communicator; + const Ice::LoggerPtr _logger; + const TrustManagerPtr _trustManager; + + std::string _password; + CertificateVerifierPtr _verifier; + PasswordPromptPtr _prompt; + + bool _checkCertName; + int _verifyDepthMax; + int _verifyPeer; + int _securityTraceLevel; + std::string _securityTraceCategory; +}; + +#ifdef ICE_USE_SECURE_TRANSPORT + +class SecureTransportEngine : public SSLEngine +{ +public: + + SecureTransportEngine(const Ice::CommunicatorPtr&); + + virtual void initialize(); + virtual bool initialized() const; + virtual void destroy(); + + ContextRef newContext(bool); + CFArrayRef getCertificateAuthorities() const; + std::string getCipherName(SSLCipherSuite) const; + SecCertificateRef getCertificate() const; + SecKeychainRef getKeychain() const; + +private: + + void parseCiphers(const std::string& ciphers); + + bool _initialized; + ContextRef _ctx; + CFArrayRef _certificateAuthorities; + SecCertificateRef _cert; + SecKeyRef _key; + SecIdentityRef _identity; + SecKeychainRef _keychain; + + SSLProtocol _protocolVersionMax; + SSLProtocol _protocolVersionMin; + + std::string _defaultDir; + + + IceUtil::UniquePtr< IceUtil::ScopedArray<char> > _dhParams; + size_t _dhParamsLength; + + IceUtil::UniquePtr< IceUtil::ScopedArray<SSLCipherSuite> > _ciphers; + bool _allCiphers; + size_t _numCiphers; + IceUtil::Mutex _mutex; +}; + +#else +class OpenSSLEngine : public SSLEngine +{ +public: + + OpenSSLEngine(const Ice::CommunicatorPtr&); + ~OpenSSLEngine(); + + virtual void initialize(); + virtual bool initialized() const; + virtual void destroy(); + virtual void verifyPeer(SSL*, SOCKET, const std::string&, const NativeConnectionInfoPtr&); + virtual void traceConnection(); + + int verifyCallback(int , SSL*, X509_STORE_CTX*); +# ifndef OPENSSL_NO_DH + DH* dhParams(int); +# endif + ContextRef context() const; + void context(ContextRef); + std::string sslErrors() const; + +private: + + SSL_METHOD* getMethod(int); + void setOptions(int); + enum Protocols { SSLv3 = 0x01, TLSv1_0 = 0x02, TLSv1_1 = 0x04, TLSv1_2 = 0x08 }; + int parseProtocols(const Ice::StringSeq&) const; + + + bool _initOpenSSL; + bool _initialized; + ContextRef _ctx; + std::string _defaultDir; + +# ifndef OPENSSL_NO_DH + DHParamsPtr _dhParams; +# endif + IceUtil::Mutex _mutex; +}; +#endif + +} + +#endif diff --git a/cpp/src/IceSSL/SSLEngineF.h b/cpp/src/IceSSL/SSLEngineF.h new file mode 100644 index 00000000000..c064c2f4250 --- /dev/null +++ b/cpp/src/IceSSL/SSLEngineF.h @@ -0,0 +1,37 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2014 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. +// +// ********************************************************************** + +#ifndef ICE_SSL_ENGINE_F_H +#define ICE_SSL_ENGINE_F_H + +#include <IceUtil/Shared.h> +#include <Ice/Handle.h> + +#include <IceSSL/Plugin.h> + +namespace IceSSL +{ + +class SSLEngine; +ICE_SSL_API IceUtil::Shared* upCast(IceSSL::SSLEngine*); +typedef IceInternal::Handle<SSLEngine> SSLEnginePtr; + +# ifdef ICE_USE_SECURE_TRANSPORT +class SecureTransportEngine; +ICE_SSL_API IceUtil::Shared* upCast(IceSSL::SecureTransportEngine*); +typedef IceInternal::Handle<SecureTransportEngine> SecureTransportEnginePtr; +# else +class OpenSSLEngine; +ICE_SSL_API IceUtil::Shared* upCast(IceSSL::OpenSSLEngine*); +typedef IceInternal::Handle<OpenSSLEngine> OpenSSLEnginePtr; +# endif + +} + +#endif diff --git a/cpp/src/IceSSL/SecureTransportEngine.cpp b/cpp/src/IceSSL/SecureTransportEngine.cpp new file mode 100644 index 00000000000..5bf401947bd --- /dev/null +++ b/cpp/src/IceSSL/SecureTransportEngine.cpp @@ -0,0 +1,1515 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2014 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/Config.h> + +#include <IceUtil/FileUtil.h> +#include <IceUtil/StringUtil.h> + +#include <Ice/LocalException.h> +#include <Ice/Properties.h> +#include <Ice/Communicator.h> +#include <Ice/Logger.h> +#include <Ice/LoggerUtil.h> + +#include <IceSSL/SecureTransportTransceiverI.h> +#include <IceSSL/Plugin.h> +#include <IceSSL/SSLEngine.h> +#include <IceSSL/Util.h> + +#ifdef ICE_USE_SECURE_TRANSPORT + +#include <regex.h> +#include <dirent.h> + +using namespace std; +using namespace IceUtil; +using namespace Ice; +using namespace IceSSL; + +namespace +{ + +vector<string> +dir(const string& path) +{ + vector<string> result; + + DIR* d = opendir(path.c_str()); + if(!d) + { + ostringstream os; + os << "failed to open dir `" << path << "'"; + throw IceUtil::IllegalArgumentException(__FILE__, __LINE__, os.str()); + } + + struct dirent* dp = 0; + while((dp = readdir(d))) + { + string name(dp->d_name); + if(IceUtilInternal::fileExists(path + "/" + name)) + { + result.push_back(name); + } + } + closedir(d); + return result; +} + +class RegExp : public IceUtil::Shared +{ +public: + + RegExp(const std::string&); + ~RegExp(); + bool match(const std::string&); + +private: + + regex_t _preg; +}; +typedef IceUtil::Handle<RegExp> RegExpPtr; + +RegExp::RegExp(const std::string& regexp) +{ + int err = regcomp(&_preg, regexp.c_str(), REG_EXTENDED | REG_NOSUB); + if(err != 0) + { + ostringstream os; + os << "failed to compiler regular expression `" << regexp << "' (error = " << err << ")"; + throw IllegalArgumentException(__FILE__, __LINE__, os.str()); + } +} + +RegExp::~RegExp() +{ + regfree(&_preg); +} + +bool +RegExp::match(const std::string& value) +{ + return regexec(&_preg, value.c_str(), 0, 0, 0) == 0; +} + +struct CipherExpression +{ + bool negation; + string cipher; + RegExpPtr re; +}; + +class CiphersHelper +{ +public: + + static void initialize(); + static SSLCipherSuite cipherForName(const string& name); + static string cipherName(SSLCipherSuite cipher); + +private: + + static map<string, SSLCipherSuite> _ciphers; +}; + +map<string, SSLCipherSuite> CiphersHelper::_ciphers; + +// +// Initialize a dictionary with the names of ciphers +// +void +CiphersHelper::initialize() +{ + _ciphers["NULL_WITH_NULL_NULL"] = SSL_NULL_WITH_NULL_NULL; + _ciphers["RSA_WITH_NULL_MD5"] = SSL_RSA_WITH_NULL_MD5; + _ciphers["RSA_WITH_NULL_SHA"] = SSL_RSA_WITH_NULL_SHA; + _ciphers["RSA_EXPORT_WITH_RC4_40_MD5"] = SSL_RSA_EXPORT_WITH_RC4_40_MD5; + _ciphers["RSA_WITH_RC4_128_MD5"] = SSL_RSA_WITH_RC4_128_MD5; + _ciphers["RSA_WITH_RC4_128_SHA"] = SSL_RSA_WITH_RC4_128_SHA; + _ciphers["RSA_EXPORT_WITH_RC2_CBC_40_MD5"] = SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5; + _ciphers["RSA_WITH_IDEA_CBC_SHA"] = SSL_RSA_WITH_IDEA_CBC_SHA; + _ciphers["RSA_EXPORT_WITH_DES40_CBC_SHA"] = SSL_RSA_EXPORT_WITH_DES40_CBC_SHA; + _ciphers["RSA_WITH_DES_CBC_SHA"] = SSL_RSA_WITH_DES_CBC_SHA; + _ciphers["RSA_WITH_3DES_EDE_CBC_SHA"] = SSL_RSA_WITH_3DES_EDE_CBC_SHA; + _ciphers["DH_DSS_EXPORT_WITH_DES40_CBC_SHA"] = SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA; + _ciphers["DH_DSS_WITH_DES_CBC_SHA"] = SSL_DH_DSS_WITH_DES_CBC_SHA; + _ciphers["DH_DSS_WITH_3DES_EDE_CBC_SHA"] = SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA; + _ciphers["DH_RSA_EXPORT_WITH_DES40_CBC_SHA"] = SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA; + _ciphers["DH_RSA_WITH_DES_CBC_SHA"] = SSL_DH_RSA_WITH_DES_CBC_SHA; + _ciphers["DH_RSA_WITH_3DES_EDE_CBC_SHA"] = SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA; + _ciphers["DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"] = SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA; + _ciphers["DHE_DSS_WITH_DES_CBC_SHA"] = SSL_DHE_DSS_WITH_DES_CBC_SHA; + _ciphers["DHE_DSS_WITH_3DES_EDE_CBC_SHA"] = SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA; + _ciphers["DHE_RSA_EXPORT_WITH_DES40_CBC_SHA"] = SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA; + _ciphers["DHE_RSA_WITH_DES_CBC_SHA"] = SSL_DHE_RSA_WITH_DES_CBC_SHA; + _ciphers["DHE_RSA_WITH_3DES_EDE_CBC_SHA"] = SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA; + _ciphers["DH_anon_EXPORT_WITH_RC4_40_MD5"] = SSL_DH_anon_EXPORT_WITH_RC4_40_MD5; + _ciphers["DH_anon_WITH_RC4_128_MD5"] = SSL_DH_anon_WITH_RC4_128_MD5; + _ciphers["DH_anon_EXPORT_WITH_DES40_CBC_SHA"] = SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA; + _ciphers["DH_anon_WITH_DES_CBC_SHA"] = SSL_DH_anon_WITH_DES_CBC_SHA; + _ciphers["DH_anon_WITH_3DES_EDE_CBC_SHA"] = SSL_DH_anon_WITH_3DES_EDE_CBC_SHA; + _ciphers["FORTEZZA_DMS_WITH_NULL_SHA"] = SSL_FORTEZZA_DMS_WITH_NULL_SHA; + _ciphers["FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA"] = SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA; + + // + // TLS addenda using AES, per RFC 3268 + // + _ciphers["RSA_WITH_AES_128_CBC_SHA"] = TLS_RSA_WITH_AES_128_CBC_SHA; + _ciphers["DH_DSS_WITH_AES_128_CBC_SHA"] = TLS_DH_DSS_WITH_AES_128_CBC_SHA; + _ciphers["DH_RSA_WITH_AES_128_CBC_SHA"] = TLS_DH_RSA_WITH_AES_128_CBC_SHA; + _ciphers["DHE_DSS_WITH_AES_128_CBC_SHA"] = TLS_DHE_DSS_WITH_AES_128_CBC_SHA; + _ciphers["DHE_RSA_WITH_AES_128_CBC_SHA"] = TLS_DHE_RSA_WITH_AES_128_CBC_SHA; + _ciphers["DH_anon_WITH_AES_128_CBC_SHA"] = TLS_DH_anon_WITH_AES_128_CBC_SHA; + _ciphers["RSA_WITH_AES_256_CBC_SHA"] = TLS_RSA_WITH_AES_256_CBC_SHA; + _ciphers["DH_DSS_WITH_AES_256_CBC_SHA"] = TLS_DH_DSS_WITH_AES_256_CBC_SHA; + _ciphers["DH_RSA_WITH_AES_256_CBC_SHA"] = TLS_DH_RSA_WITH_AES_256_CBC_SHA; + _ciphers["DHE_DSS_WITH_AES_256_CBC_SHA"] = TLS_DHE_DSS_WITH_AES_256_CBC_SHA; + _ciphers["DHE_RSA_WITH_AES_256_CBC_SHA"] = TLS_DHE_RSA_WITH_AES_256_CBC_SHA; + _ciphers["DH_anon_WITH_AES_256_CBC_SHA"] = TLS_DH_anon_WITH_AES_256_CBC_SHA; + + // + // ECDSA addenda, RFC 4492 + // + _ciphers["ECDH_ECDSA_WITH_NULL_SHA"] = TLS_ECDH_ECDSA_WITH_NULL_SHA; + _ciphers["ECDH_ECDSA_WITH_RC4_128_SHA"] = TLS_ECDH_ECDSA_WITH_RC4_128_SHA; + _ciphers["ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA"] = TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA; + _ciphers["ECDH_ECDSA_WITH_AES_128_CBC_SHA"] = TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA; + _ciphers["ECDH_ECDSA_WITH_AES_256_CBC_SHA"] = TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA; + _ciphers["ECDHE_ECDSA_WITH_NULL_SHA"] = TLS_ECDHE_ECDSA_WITH_NULL_SHA; + _ciphers["ECDHE_ECDSA_WITH_RC4_128_SHA"] = TLS_ECDHE_ECDSA_WITH_RC4_128_SHA; + _ciphers["ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA"] = TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA; + _ciphers["ECDHE_ECDSA_WITH_AES_128_CBC_SHA"] = TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA; + _ciphers["ECDHE_ECDSA_WITH_AES_256_CBC_SHA"] = TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA; + _ciphers["ECDH_RSA_WITH_NULL_SHA"] = TLS_ECDH_RSA_WITH_NULL_SHA; + _ciphers["ECDH_RSA_WITH_RC4_128_SHA"] = TLS_ECDH_RSA_WITH_RC4_128_SHA; + _ciphers["ECDH_RSA_WITH_3DES_EDE_CBC_SHA"] = TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA; + _ciphers["ECDH_RSA_WITH_AES_128_CBC_SHA"] = TLS_ECDH_RSA_WITH_AES_128_CBC_SHA; + _ciphers["ECDH_RSA_WITH_AES_256_CBC_SHA"] = TLS_ECDH_RSA_WITH_AES_256_CBC_SHA; + _ciphers["ECDHE_RSA_WITH_NULL_SHA"] = TLS_ECDHE_RSA_WITH_NULL_SHA; + _ciphers["ECDHE_RSA_WITH_RC4_128_SHA"] = TLS_ECDHE_RSA_WITH_RC4_128_SHA; + _ciphers["ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"] = TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA; + _ciphers["ECDHE_RSA_WITH_AES_128_CBC_SHA"] = TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA; + _ciphers["ECDHE_RSA_WITH_AES_256_CBC_SHA"] = TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA; + _ciphers["ECDH_anon_WITH_NULL_SHA"] = TLS_ECDH_anon_WITH_NULL_SHA; + _ciphers["ECDH_anon_WITH_RC4_128_SHA"] = TLS_ECDH_anon_WITH_RC4_128_SHA; + _ciphers["ECDH_anon_WITH_3DES_EDE_CBC_SHA"] = TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA; + _ciphers["ECDH_anon_WITH_AES_128_CBC_SHA"] = TLS_ECDH_anon_WITH_AES_128_CBC_SHA; + _ciphers["ECDH_anon_WITH_AES_256_CBC_SHA"] = TLS_ECDH_anon_WITH_AES_256_CBC_SHA; + + // + // TLS 1.2 addenda, RFC 5246 + // + //_ciphers["NULL_WITH_NULL_NULL"] = TLS_NULL_WITH_NULL_NULL; + + // + // Server provided RSA certificate for key exchange. + // + //_ciphers["RSA_WITH_NULL_MD5"] = TLS_RSA_WITH_NULL_MD5; + //_ciphers["RSA_WITH_NULL_SHA"] = TLS_RSA_WITH_NULL_SHA; + //_ciphers["RSA_WITH_RC4_128_MD5"] = TLS_RSA_WITH_RC4_128_MD5; + //_ciphers["RSA_WITH_RC4_128_SHA"] = TLS_RSA_WITH_RC4_128_SHA; + //_ciphers["RSA_WITH_3DES_EDE_CBC_SHA"] = TLS_RSA_WITH_3DES_EDE_CBC_SHA; + _ciphers["RSA_WITH_NULL_SHA256"] = TLS_RSA_WITH_NULL_SHA256; + _ciphers["RSA_WITH_AES_128_CBC_SHA256"] = TLS_RSA_WITH_AES_128_CBC_SHA256; + _ciphers["RSA_WITH_AES_256_CBC_SHA256"] = TLS_RSA_WITH_AES_256_CBC_SHA256; + + // + // Server-authenticated (and optionally client-authenticated) Diffie-Hellman. + // + //_ciphers["DH_DSS_WITH_3DES_EDE_CBC_SHA"] = TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA; + //_ciphers["DH_RSA_WITH_3DES_EDE_CBC_SHA"] = TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA; + //_ciphers["DHE_DSS_WITH_3DES_EDE_CBC_SHA"] = TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA; + //_ciphers["DHE_RSA_WITH_3DES_EDE_CBC_SHA"] = TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA; + _ciphers["DH_DSS_WITH_AES_128_CBC_SHA256"] = TLS_DH_DSS_WITH_AES_128_CBC_SHA256; + _ciphers["DH_RSA_WITH_AES_128_CBC_SHA256"] = TLS_DH_RSA_WITH_AES_128_CBC_SHA256; + _ciphers["DHE_DSS_WITH_AES_128_CBC_SHA256"] = TLS_DHE_DSS_WITH_AES_128_CBC_SHA256; + _ciphers["DHE_RSA_WITH_AES_128_CBC_SHA256"] = TLS_DHE_RSA_WITH_AES_128_CBC_SHA256; + _ciphers["DH_DSS_WITH_AES_256_CBC_SHA256"] = TLS_DH_DSS_WITH_AES_256_CBC_SHA256; + _ciphers["DH_RSA_WITH_AES_256_CBC_SHA256"] = TLS_DH_RSA_WITH_AES_256_CBC_SHA256; + _ciphers["DHE_DSS_WITH_AES_256_CBC_SHA256"] = TLS_DHE_DSS_WITH_AES_256_CBC_SHA256; + _ciphers["DHE_RSA_WITH_AES_256_CBC_SHA256"] = TLS_DHE_RSA_WITH_AES_256_CBC_SHA256; + + // + // Completely anonymous Diffie-Hellman + // + //_ciphers["DH_anon_WITH_RC4_128_MD5"] = TLS_DH_anon_WITH_RC4_128_MD5; + //_ciphers["DH_anon_WITH_3DES_EDE_CBC_SHA"] = TLS_DH_anon_WITH_3DES_EDE_CBC_SHA; + _ciphers["DH_anon_WITH_AES_128_CBC_SHA256"] = TLS_DH_anon_WITH_AES_128_CBC_SHA256; + _ciphers["DH_anon_WITH_AES_256_CBC_SHA256"] = TLS_DH_anon_WITH_AES_256_CBC_SHA256; + + // + // Addendum from RFC 4279, TLS PSK + // + _ciphers["PSK_WITH_RC4_128_SHA"] = TLS_PSK_WITH_RC4_128_SHA; + _ciphers["PSK_WITH_3DES_EDE_CBC_SHA"] = TLS_PSK_WITH_3DES_EDE_CBC_SHA; + _ciphers["PSK_WITH_AES_128_CBC_SHA"] = TLS_PSK_WITH_AES_128_CBC_SHA; + _ciphers["PSK_WITH_AES_256_CBC_SHA"] = TLS_PSK_WITH_AES_256_CBC_SHA; + _ciphers["DHE_PSK_WITH_RC4_128_SHA"] = TLS_DHE_PSK_WITH_RC4_128_SHA; + _ciphers["DHE_PSK_WITH_3DES_EDE_CBC_SHA"] = TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA; + _ciphers["DHE_PSK_WITH_AES_128_CBC_SHA"] = TLS_DHE_PSK_WITH_AES_128_CBC_SHA; + _ciphers["DHE_PSK_WITH_AES_256_CBC_SHA"] = TLS_DHE_PSK_WITH_AES_256_CBC_SHA; + _ciphers["RSA_PSK_WITH_RC4_128_SHA"] = TLS_RSA_PSK_WITH_RC4_128_SHA; + _ciphers["RSA_PSK_WITH_3DES_EDE_CBC_SHA"] = TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA; + _ciphers["RSA_PSK_WITH_AES_128_CBC_SHA"] = TLS_RSA_PSK_WITH_AES_128_CBC_SHA; + _ciphers["RSA_PSK_WITH_AES_256_CBC_SHA"] = TLS_RSA_PSK_WITH_AES_256_CBC_SHA; + + // + // RFC 4785 - Pre-Shared Key (PSK) Ciphersuites with NULL Encryption + // + _ciphers["PSK_WITH_NULL_SHA"] = TLS_PSK_WITH_NULL_SHA; + _ciphers["DHE_PSK_WITH_NULL_SHA"] = TLS_DHE_PSK_WITH_NULL_SHA; + _ciphers["RSA_PSK_WITH_NULL_SHA"] = TLS_RSA_PSK_WITH_NULL_SHA; + + // + // Addenda from rfc 5288 AES Galois Counter Mode (GCM) Cipher Suites for TLS. + // + _ciphers["RSA_WITH_AES_128_GCM_SHA256"] = TLS_RSA_WITH_AES_128_GCM_SHA256; + _ciphers["RSA_WITH_AES_256_GCM_SHA384"] = TLS_RSA_WITH_AES_256_GCM_SHA384; + _ciphers["DHE_RSA_WITH_AES_128_GCM_SHA256"] = TLS_DHE_RSA_WITH_AES_128_GCM_SHA256; + _ciphers["DHE_RSA_WITH_AES_256_GCM_SHA384"] = TLS_DHE_RSA_WITH_AES_256_GCM_SHA384; + _ciphers["DH_RSA_WITH_AES_128_GCM_SHA256"] = TLS_DH_RSA_WITH_AES_128_GCM_SHA256; + _ciphers["DH_RSA_WITH_AES_256_GCM_SHA384"] = TLS_DH_RSA_WITH_AES_256_GCM_SHA384; + _ciphers["DHE_DSS_WITH_AES_128_GCM_SHA256"] = TLS_DHE_DSS_WITH_AES_128_GCM_SHA256; + _ciphers["DHE_DSS_WITH_AES_256_GCM_SHA384"] = TLS_DHE_DSS_WITH_AES_256_GCM_SHA384; + _ciphers["DH_DSS_WITH_AES_128_GCM_SHA256"] = TLS_DH_DSS_WITH_AES_128_GCM_SHA256; + _ciphers["DH_DSS_WITH_AES_256_GCM_SHA384"] = TLS_DH_DSS_WITH_AES_256_GCM_SHA384; + _ciphers["DH_anon_WITH_AES_128_GCM_SHA256"] = TLS_DH_anon_WITH_AES_128_GCM_SHA256; + _ciphers["DH_anon_WITH_AES_256_GCM_SHA384"] = TLS_DH_anon_WITH_AES_256_GCM_SHA384; + + // + // RFC 5487 - PSK with SHA-256/384 and AES GCM + // + _ciphers["PSK_WITH_AES_128_GCM_SHA256"] = TLS_PSK_WITH_AES_128_GCM_SHA256; + _ciphers["PSK_WITH_AES_256_GCM_SHA384"] = TLS_PSK_WITH_AES_256_GCM_SHA384; + _ciphers["DHE_PSK_WITH_AES_128_GCM_SHA256"] = TLS_DHE_PSK_WITH_AES_128_GCM_SHA256; + _ciphers["DHE_PSK_WITH_AES_256_GCM_SHA384"] = TLS_DHE_PSK_WITH_AES_256_GCM_SHA384; + _ciphers["RSA_PSK_WITH_AES_128_GCM_SHA256"] = TLS_RSA_PSK_WITH_AES_128_GCM_SHA256; + _ciphers["RSA_PSK_WITH_AES_256_GCM_SHA384"] = TLS_RSA_PSK_WITH_AES_256_GCM_SHA384; + + _ciphers["PSK_WITH_AES_128_CBC_SHA256"] = TLS_PSK_WITH_AES_128_CBC_SHA256; + _ciphers["PSK_WITH_AES_256_CBC_SHA384"] = TLS_PSK_WITH_AES_256_CBC_SHA384; + _ciphers["PSK_WITH_NULL_SHA256"] = TLS_PSK_WITH_NULL_SHA256; + _ciphers["PSK_WITH_NULL_SHA384"] = TLS_PSK_WITH_NULL_SHA384; + + _ciphers["DHE_PSK_WITH_AES_128_CBC_SHA256"] = TLS_DHE_PSK_WITH_AES_128_CBC_SHA256; + _ciphers["DHE_PSK_WITH_AES_256_CBC_SHA384"] = TLS_DHE_PSK_WITH_AES_256_CBC_SHA384; + _ciphers["DHE_PSK_WITH_NULL_SHA256"] = TLS_DHE_PSK_WITH_NULL_SHA256; + _ciphers["DHE_PSK_WITH_NULL_SHA384"] = TLS_DHE_PSK_WITH_NULL_SHA384; + + _ciphers["RSA_PSK_WITH_AES_128_CBC_SHA256"] = TLS_RSA_PSK_WITH_AES_128_CBC_SHA256; + _ciphers["RSA_PSK_WITH_AES_256_CBC_SHA384"] = TLS_RSA_PSK_WITH_AES_256_CBC_SHA384; + _ciphers["RSA_PSK_WITH_NULL_SHA256"] = TLS_RSA_PSK_WITH_NULL_SHA256; + _ciphers["RSA_PSK_WITH_NULL_SHA384"] = TLS_RSA_PSK_WITH_NULL_SHA384; + + // + // Addenda from rfc 5289 Elliptic Curve Cipher Suites with HMAC SHA-256/384. + // + _ciphers["ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"] = TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; + _ciphers["ECDHE_ECDSA_WITH_AES_256_CBC_SHA384"] = TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384; + _ciphers["ECDH_ECDSA_WITH_AES_128_CBC_SHA256"] = TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256; + _ciphers["ECDH_ECDSA_WITH_AES_256_CBC_SHA384"] = TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384; + _ciphers["ECDHE_RSA_WITH_AES_128_CBC_SHA256"] = TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256; + _ciphers["ECDHE_RSA_WITH_AES_256_CBC_SHA384"] = TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384; + _ciphers["ECDH_RSA_WITH_AES_128_CBC_SHA256"] = TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256; + _ciphers["ECDH_RSA_WITH_AES_256_CBC_SHA384"] = TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384; + + // + // Addenda from rfc 5289 Elliptic Curve Cipher Suites with SHA-256/384 and AES Galois Counter Mode (GCM) + // + _ciphers["ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"] = TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256; + _ciphers["ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"] = TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384; + _ciphers["ECDH_ECDSA_WITH_AES_128_GCM_SHA256"] = TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256; + _ciphers["ECDH_ECDSA_WITH_AES_256_GCM_SHA384"] = TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384; + _ciphers["ECDHE_RSA_WITH_AES_128_GCM_SHA256"] = TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256; + _ciphers["ECDHE_RSA_WITH_AES_256_GCM_SHA384"] = TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384; + _ciphers["ECDH_RSA_WITH_AES_128_GCM_SHA256"] = TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256; + _ciphers["ECDH_RSA_WITH_AES_256_GCM_SHA384"] = TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384; + + // + // RFC 5746 - Secure Renegotiation + // + _ciphers["EMPTY_RENEGOTIATION_INFO_SCSV"] = TLS_EMPTY_RENEGOTIATION_INFO_SCSV; + + // + // Tags for SSL 2 cipher kinds that are not specified for SSL 3. + // + _ciphers["RSA_WITH_RC2_CBC_MD5"] = SSL_RSA_WITH_RC2_CBC_MD5; + _ciphers["RSA_WITH_IDEA_CBC_MD5"] = SSL_RSA_WITH_IDEA_CBC_MD5; + _ciphers["RSA_WITH_DES_CBC_MD5"] = SSL_RSA_WITH_DES_CBC_MD5; + _ciphers["RSA_WITH_3DES_EDE_CBC_MD5"] = SSL_RSA_WITH_3DES_EDE_CBC_MD5; + _ciphers["NO_SUCH_CIPHERSUITE"] = SSL_NO_SUCH_CIPHERSUITE; +} + +SSLCipherSuite +CiphersHelper::cipherForName(const string& name) +{ + map<string, SSLCipherSuite>::const_iterator i = _ciphers.find(name); + if(i == _ciphers.end() || i->second == SSL_NO_SUCH_CIPHERSUITE) + { + PluginInitializationException ex(__FILE__, __LINE__, "IceSSL: no such cipher " + name); + throw ex; + } + return i->second; +} + +// +// Retrive the name of a cipher, SSLCipherSuite inlude duplicated values for TLS/SSL +// protocol ciphers, for example SSL_RSA_WITH_RC4_128_MD5/TLS_RSA_WITH_RC4_128_MD5 +// are represeted by the same SSLCipherSuite value, the names return by this method +// doesn't include a protocol prefix. +// +string +CiphersHelper::cipherName(SSLCipherSuite cipher) +{ + switch(cipher) + { + case SSL_NULL_WITH_NULL_NULL: + return "NULL_WITH_NULL_NULL"; + case SSL_RSA_WITH_NULL_MD5: + return "RSA_WITH_NULL_MD5"; + case SSL_RSA_WITH_NULL_SHA: + return "RSA_WITH_NULL_SHA"; + case SSL_RSA_EXPORT_WITH_RC4_40_MD5: + return "RSA_EXPORT_WITH_RC4_40_MD5"; + case SSL_RSA_WITH_RC4_128_MD5: + return "RSA_WITH_RC4_128_MD5"; + case SSL_RSA_WITH_RC4_128_SHA: + return "RSA_WITH_RC4_128_SHA"; + case SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5: + return "RSA_EXPORT_WITH_RC2_CBC_40_MD5"; + case SSL_RSA_WITH_IDEA_CBC_SHA: + return "RSA_WITH_IDEA_CBC_SHA"; + case SSL_RSA_EXPORT_WITH_DES40_CBC_SHA: + return "RSA_EXPORT_WITH_DES40_CBC_SHA"; + case SSL_RSA_WITH_DES_CBC_SHA: + return "RSA_WITH_DES_CBC_SHA"; + case SSL_RSA_WITH_3DES_EDE_CBC_SHA: + return "RSA_WITH_3DES_EDE_CBC_SHA"; + case SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA: + return "DH_DSS_EXPORT_WITH_DES40_CBC_SHA"; + case SSL_DH_DSS_WITH_DES_CBC_SHA: + return "DH_DSS_WITH_DES_CBC_SHA"; + case SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA: + return "DH_DSS_WITH_3DES_EDE_CBC_SHA"; + case SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA: + return "DH_RSA_EXPORT_WITH_DES40_CBC_SHA"; + case SSL_DH_RSA_WITH_DES_CBC_SHA: + return "DH_RSA_WITH_DES_CBC_SHA"; + case SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA: + return "DH_RSA_WITH_3DES_EDE_CBC_SHA"; + case SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA: + return "DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"; + case SSL_DHE_DSS_WITH_DES_CBC_SHA: + return "DHE_DSS_WITH_DES_CBC_SHA"; + case SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + return "DHE_DSS_WITH_3DES_EDE_CBC_SHA"; + case SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA: + return "DHE_RSA_EXPORT_WITH_DES40_CBC_SHA"; + case SSL_DHE_RSA_WITH_DES_CBC_SHA: + return "DHE_RSA_WITH_DES_CBC_SHA"; + case SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + return "DHE_RSA_WITH_3DES_EDE_CBC_SHA"; + case SSL_DH_anon_EXPORT_WITH_RC4_40_MD5: + return "DH_anon_EXPORT_WITH_RC4_40_MD5"; + case SSL_DH_anon_WITH_RC4_128_MD5: + return "DH_anon_WITH_RC4_128_MD5"; + case SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA: + return "DH_anon_EXPORT_WITH_DES40_CBC_SHA"; + case SSL_DH_anon_WITH_DES_CBC_SHA: + return "DH_anon_WITH_DES_CBC_SHA"; + case SSL_DH_anon_WITH_3DES_EDE_CBC_SHA: + return "DH_anon_WITH_3DES_EDE_CBC_SHA"; + case SSL_FORTEZZA_DMS_WITH_NULL_SHA: + return "FORTEZZA_DMS_WITH_NULL_SHA"; + case SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA: + return "FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA"; + + // + // TLS addenda using AES, per RFC 3268 + // + case TLS_RSA_WITH_AES_128_CBC_SHA: + return "RSA_WITH_AES_128_CBC_SHA"; + case TLS_DH_DSS_WITH_AES_128_CBC_SHA: + return "DH_DSS_WITH_AES_128_CBC_SHA"; + case TLS_DH_RSA_WITH_AES_128_CBC_SHA: + return "DH_RSA_WITH_AES_128_CBC_SHA"; + case TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + return "DHE_DSS_WITH_AES_128_CBC_SHA"; + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + return "DHE_RSA_WITH_AES_128_CBC_SHA"; + case TLS_DH_anon_WITH_AES_128_CBC_SHA: + return "DH_anon_WITH_AES_128_CBC_SHA"; + case TLS_RSA_WITH_AES_256_CBC_SHA: + return "RSA_WITH_AES_256_CBC_SHA"; + case TLS_DH_DSS_WITH_AES_256_CBC_SHA: + return "DH_DSS_WITH_AES_256_CBC_SHA"; + case TLS_DH_RSA_WITH_AES_256_CBC_SHA: + return "DH_RSA_WITH_AES_256_CBC_SHA"; + case TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + return "DHE_DSS_WITH_AES_256_CBC_SHA"; + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + return "DHE_RSA_WITH_AES_256_CBC_SHA"; + case TLS_DH_anon_WITH_AES_256_CBC_SHA: + return "DH_anon_WITH_AES_256_CBC_SHA"; + + // + // ECDSA addenda, RFC 4492 + // + case TLS_ECDH_ECDSA_WITH_NULL_SHA: + return "ECDH_ECDSA_WITH_NULL_SHA"; + case TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + return "ECDH_ECDSA_WITH_RC4_128_SHA"; + case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + return "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + return "ECDH_ECDSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + return "ECDH_ECDSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDHE_ECDSA_WITH_NULL_SHA: + return "ECDHE_ECDSA_WITH_NULL_SHA"; + case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + return "ECDHE_ECDSA_WITH_RC4_128_SHA"; + case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + return "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDH_RSA_WITH_NULL_SHA: + return "ECDH_RSA_WITH_NULL_SHA"; + case TLS_ECDH_RSA_WITH_RC4_128_SHA: + return "ECDH_RSA_WITH_RC4_128_SHA"; + case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + return "ECDH_RSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + return "ECDH_RSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + return "ECDH_RSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDHE_RSA_WITH_NULL_SHA: + return "ECDHE_RSA_WITH_NULL_SHA"; + case TLS_ECDHE_RSA_WITH_RC4_128_SHA: + return "ECDHE_RSA_WITH_RC4_128_SHA"; + case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + return "ECDHE_RSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + return "ECDHE_RSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDH_anon_WITH_NULL_SHA: + return "ECDH_anon_WITH_NULL_SHA"; + case TLS_ECDH_anon_WITH_RC4_128_SHA: + return "ECDH_anon_WITH_RC4_128_SHA"; + case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA: + return "ECDH_anon_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDH_anon_WITH_AES_128_CBC_SHA: + return "ECDH_anon_WITH_AES_128_CBC_SHA"; + case TLS_ECDH_anon_WITH_AES_256_CBC_SHA: + return "ECDH_anon_WITH_AES_256_CBC_SHA"; + + // + // TLS 1.2 addenda, RFC 5246 + // + //case TLS_NULL_WITH_NULL_NULL: + // return "NULL_WITH_NULL_NULL"; + + // + // Server provided RSA certificate for key exchange. + // + //case TLS_RSA_WITH_NULL_MD5: + // return "RSA_WITH_NULL_MD5"; + //case TLS_RSA_WITH_NULL_SHA: + // return "RSA_WITH_NULL_SHA"; + //case TLS_RSA_WITH_RC4_128_MD5: + // return "RSA_WITH_RC4_128_MD5"; + //case TLS_RSA_WITH_RC4_128_SHA: + // return "RSA_WITH_RC4_128_SHA"; + //case TLS_RSA_WITH_3DES_EDE_CBC_SHA: + // return "RSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_RSA_WITH_NULL_SHA256: + return "RSA_WITH_NULL_SHA256"; + case TLS_RSA_WITH_AES_128_CBC_SHA256: + return "RSA_WITH_AES_128_CBC_SHA256"; + case TLS_RSA_WITH_AES_256_CBC_SHA256: + return "RSA_WITH_AES_256_CBC_SHA256"; + + // + // Server-authenticated (and optionally client-authenticated) Diffie-Hellman. + // + //case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: + // return "DH_DSS_WITH_3DES_EDE_CBC_SHA"; + //case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA: + // return "DH_RSA_WITH_3DES_EDE_CBC_SHA"; + //case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + // return "DHE_DSS_WITH_3DES_EDE_CBC_SHA"; + //case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + // return "DHE_RSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_DH_DSS_WITH_AES_128_CBC_SHA256: + return "DH_DSS_WITH_AES_128_CBC_SHA256"; + case TLS_DH_RSA_WITH_AES_128_CBC_SHA256: + return "DH_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + return "DHE_DSS_WITH_AES_128_CBC_SHA256"; + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + return "DHE_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + return "DH_DSS_WITH_AES_256_CBC_SHA256"; + case TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + return "DH_RSA_WITH_AES_256_CBC_SHA256"; + case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + return "DHE_DSS_WITH_AES_256_CBC_SHA256"; + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + return "DHE_RSA_WITH_AES_256_CBC_SHA256"; + + // + // Completely anonymous Diffie-Hellman + // + //case TLS_DH_anon_WITH_RC4_128_MD5: + // return "DH_anon_WITH_RC4_128_MD5"; + //case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA: + // return "DH_anon_WITH_3DES_EDE_CBC_SHA"; + case TLS_DH_anon_WITH_AES_128_CBC_SHA256: + return "DH_anon_WITH_AES_128_CBC_SHA256"; + case TLS_DH_anon_WITH_AES_256_CBC_SHA256: + return "DH_anon_WITH_AES_256_CBC_SHA256"; + + // + // Addendum from RFC 4279, TLS PSK + // + case TLS_PSK_WITH_RC4_128_SHA: + return "PSK_WITH_RC4_128_SHA"; + case TLS_PSK_WITH_3DES_EDE_CBC_SHA: + return "PSK_WITH_3DES_EDE_CBC_SHA"; + case TLS_PSK_WITH_AES_128_CBC_SHA: + return "PSK_WITH_AES_128_CBC_SHA"; + case TLS_PSK_WITH_AES_256_CBC_SHA: + return "PSK_WITH_AES_256_CBC_SHA"; + case TLS_DHE_PSK_WITH_RC4_128_SHA: + return "DHE_PSK_WITH_RC4_128_SHA"; + case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + return "DHE_PSK_WITH_3DES_EDE_CBC_SHA"; + case TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + return "DHE_PSK_WITH_AES_128_CBC_SHA"; + case TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + return "DHE_PSK_WITH_AES_256_CBC_SHA"; + case TLS_RSA_PSK_WITH_RC4_128_SHA: + return "RSA_PSK_WITH_RC4_128_SHA"; + case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: + return "RSA_PSK_WITH_3DES_EDE_CBC_SHA"; + case TLS_RSA_PSK_WITH_AES_128_CBC_SHA: + return "RSA_PSK_WITH_AES_128_CBC_SHA"; + case TLS_RSA_PSK_WITH_AES_256_CBC_SHA: + return "RSA_PSK_WITH_AES_256_CBC_SHA"; + + // + // RFC 4785 - Pre-Shared Key (PSK) Ciphersuites with NULL Encryption + // + case TLS_PSK_WITH_NULL_SHA: + return "PSK_WITH_NULL_SHA"; + case TLS_DHE_PSK_WITH_NULL_SHA: + return "DHE_PSK_WITH_NULL_SHA"; + case TLS_RSA_PSK_WITH_NULL_SHA: + return "RSA_PSK_WITH_NULL_SHA"; + + // + // Addenda from rfc 5288 AES Galois Counter Mode (GCM) Cipher Suites for TLS. + // + case TLS_RSA_WITH_AES_128_GCM_SHA256: + return "RSA_WITH_AES_128_GCM_SHA256"; + case TLS_RSA_WITH_AES_256_GCM_SHA384: + return "RSA_WITH_AES_256_GCM_SHA384"; + case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + return "DHE_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + return "DHE_RSA_WITH_AES_256_GCM_SHA384"; + case TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + return "DH_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_DH_RSA_WITH_AES_256_GCM_SHA384: + return "DH_RSA_WITH_AES_256_GCM_SHA384"; + case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + return "DHE_DSS_WITH_AES_128_GCM_SHA256"; + case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + return "DHE_DSS_WITH_AES_256_GCM_SHA384"; + case TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + return "DH_DSS_WITH_AES_128_GCM_SHA256"; + case TLS_DH_DSS_WITH_AES_256_GCM_SHA384: + return "DH_DSS_WITH_AES_256_GCM_SHA384"; + case TLS_DH_anon_WITH_AES_128_GCM_SHA256: + return "DH_anon_WITH_AES_128_GCM_SHA256"; + case TLS_DH_anon_WITH_AES_256_GCM_SHA384: + return "DH_anon_WITH_AES_256_GCM_SHA384"; + + // + // RFC 5487 - PSK with SHA-256/384 and AES GCM + // + case TLS_PSK_WITH_AES_128_GCM_SHA256: + return "PSK_WITH_AES_128_GCM_SHA256"; + case TLS_PSK_WITH_AES_256_GCM_SHA384: + return "PSK_WITH_AES_256_GCM_SHA384"; + case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: + return "DHE_PSK_WITH_AES_128_GCM_SHA256"; + case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: + return "DHE_PSK_WITH_AES_256_GCM_SHA384"; + case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: + return "RSA_PSK_WITH_AES_128_GCM_SHA256"; + case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: + return "RSA_PSK_WITH_AES_256_GCM_SHA384"; + + case TLS_PSK_WITH_AES_128_CBC_SHA256: + return "PSK_WITH_AES_128_CBC_SHA256"; + case TLS_PSK_WITH_AES_256_CBC_SHA384: + return "PSK_WITH_AES_256_CBC_SHA384"; + case TLS_PSK_WITH_NULL_SHA256: + return "WITH_NULL_SHA256"; + case TLS_PSK_WITH_NULL_SHA384: + return "PSK_WITH_NULL_SHA384"; + + case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256: + return "DHE_PSK_WITH_AES_128_CBC_SHA256"; + case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: + return "DHE_PSK_WITH_AES_256_CBC_SHA384"; + case TLS_DHE_PSK_WITH_NULL_SHA256: + return "DHE_PSK_WITH_NULL_SHA256"; + case TLS_DHE_PSK_WITH_NULL_SHA384: + return "DHE_PSK_WITH_NULL_SHA384"; + + case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256: + return "RSA_PSK_WITH_AES_128_CBC_SHA256"; + case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384: + return "RSA_PSK_WITH_AES_256_CBC_SHA384"; + case TLS_RSA_PSK_WITH_NULL_SHA256: + return "RSA_PSK_WITH_NULL_SHA256"; + case TLS_RSA_PSK_WITH_NULL_SHA384: + return "RSA_PSK_WITH_NULL_SHA384"; + + // + // Addenda from rfc 5289 Elliptic Curve Cipher Suites with HMAC SHA-256/384. + // + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384"; + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + return "ECDH_ECDSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + return "ECDH_ECDSA_WITH_AES_256_CBC_SHA384"; + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + return "ECDHE_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + return "ECDHE_RSA_WITH_AES_256_CBC_SHA384"; + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + return "ECDH_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + return "ECDH_RSA_WITH_AES_256_CBC_SHA384"; + + // + // Addenda from rfc 5289 Elliptic Curve Cipher Suites with SHA-256/384 and AES Galois Counter Mode (GCM) + // + case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"; + case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + return "ECDH_ECDSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + return "ECDH_ECDSA_WITH_AES_256_GCM_SHA384"; + case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + return "ECDHE_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + return "ECDHE_RSA_WITH_AES_256_GCM_SHA384"; + case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + return "ECDH_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + return "ECDH_RSA_WITH_AES_256_GCM_SHA384"; + + // + // RFC 5746 - Secure Renegotiation + // + case TLS_EMPTY_RENEGOTIATION_INFO_SCSV: + return "EMPTY_RENEGOTIATION_INFO_SCSV"; + + // + // Tags for SSL 2 cipher kinds that are not specified for SSL 3. + // + case SSL_RSA_WITH_RC2_CBC_MD5: + return "RSA_WITH_RC2_CBC_MD5"; + case SSL_RSA_WITH_IDEA_CBC_MD5: + return "RSA_WITH_IDEA_CBC_MD5"; + case SSL_RSA_WITH_DES_CBC_MD5: + return "RSA_WITH_DES_CBC_MD5"; + case SSL_RSA_WITH_3DES_EDE_CBC_MD5: + return "RSA_WITH_3DES_EDE_CBC_MD5"; + default: + return ""; + } +} + +SSLProtocol +parseProtocol(const std::string& prot) +{ + if(prot == "ssl2" || prot == "sslv2") + { + return kSSLProtocol2; + } + else if(prot == "ssl3" || prot == "sslv3") + { + return kSSLProtocol3; + } + else if(prot == "tls" || prot == "tls1" || prot == "tlsv1" || prot == "tls1_0" || prot == "tlsv1_0") + { + return kTLSProtocol1; + } + else if(prot == "tls1_1" || prot == "tlsv1_1") + { + return kTLSProtocol11; + } + else if(prot == "tls1_2" || prot == "tlsv1_2") + { + return kTLSProtocol12; + } + else + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: unrecognized protocol `" + prot + "'"; + throw ex; + } +} + +} + +IceUtil::Shared* IceSSL::upCast(IceSSL::SecureTransportEngine* p) { return p; } + +IceSSL::SecureTransportEngine::SecureTransportEngine(const Ice::CommunicatorPtr& communicator) : + SSLEngine(communicator), + _initialized(false), + _ctx(0), + _certificateAuthorities(0), + _cert(0), + _key(0), + _identity(0), + _keychain(0), + _protocolVersionMax(kSSLProtocolUnknown), + _protocolVersionMin(kSSLProtocolUnknown), + _dhParams(0), + _dhParamsLength(0), + _ciphers(new ScopedArray<SSLCipherSuite>()), + _allCiphers(false), + _numCiphers(-1) +{ +} + +bool +IceSSL::SecureTransportEngine::initialized() const +{ + IceUtil::Mutex::Lock lock(_mutex); + return _initialized; +} +// +// Setup the engine. +// +void +IceSSL::SecureTransportEngine::initialize() +{ + IceUtil::Mutex::Lock lock(_mutex); + if(_initialized) + { + return; + } + + SSLEngine::initialize(); + + const string propPrefix = "IceSSL."; + const PropertiesPtr properties = communicator()->getProperties(); + + // + // Check for a default directory. We look in this directory for + // files mentioned in the configuration. + // + string defaultDir = properties->getProperty(propPrefix + "DefaultDir"); + + // + // Open the application KeyChain or create it if the keychain doesn't exists + // + string keychainPath = properties->getProperty("IceSSL.Keychain"); + string keychainPassword = properties->getProperty("IceSSL.KeychainPassword"); + + // + // KeyChain path is relative to the current working directory. + // + if(keychainPath.empty()) + { + keychainPath = "login.keychain"; + } + else + { + if(!IceUtilInternal::isAbsolutePath(keychainPath)) + { + string cwd; + if(IceUtilInternal::getcwd(cwd) == 0) + { + keychainPath = string(cwd) + '/' + keychainPath; + } + } + } + + bool usePassword = !keychainPassword.empty(); + size_t size = keychainPassword.size(); + const char* password = usePassword ? keychainPassword.c_str() : 0; + + OSStatus err = SecKeychainOpen(keychainPath.c_str(), &_keychain); + if(err != noErr) + { + ostringstream os; + os << "IceSSL: unable to open keychain: `" << keychainPath << "'\n" << errorToString(err); + throw PluginInitializationException(__FILE__, __LINE__, os.str()); + } + + SecKeychainStatus status; + err = SecKeychainGetStatus(_keychain, &status); + + if(err == noErr) + { + err = SecKeychainUnlock(_keychain, size, password, usePassword); + if(err != noErr) + { + ostringstream os; + os << "IceSSL: unable to unlock keychain: `" << keychainPath << "'\n" << errorToString(err); + throw PluginInitializationException(__FILE__, __LINE__, os.str()); + } + } + else if(err == errSecNoSuchKeychain) + { + err = SecKeychainCreate(keychainPath.c_str(), size, password, keychainPassword.empty(), 0, &_keychain); + if(err != noErr) + { + ostringstream os; + os << "IceSSL: unable to create keychain: `" << keychainPath << "'\n" << errorToString(err); + throw PluginInitializationException(__FILE__, __LINE__, os.str()); + } + } + else + { + ostringstream os; + os << "IceSSL: unable to open keychain: `" << keychainPath << "'\n" << errorToString(err); + throw PluginInitializationException(__FILE__, __LINE__, os.str()); + } + + int passwordRetryMax = properties->getPropertyAsIntWithDefault(propPrefix + "PasswordRetryMax", 3); + PasswordPromptPtr passwordPrompt = getPasswordPrompt(); + // + // Load the CA certificates used to authenticate peers into + // _certificateAuthorities array. + // + { + try + { + string caFile = properties->getProperty(propPrefix + "CertAuthFile"); + if(!caFile.empty()) + { + if(!checkPath(caFile, defaultDir, false)) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: CA certificate file not found:\n" + caFile; + throw ex; + } + _certificateAuthorities = loadCACertificates(caFile); + } + } + catch(const CertificateReadException& ce) + { + PluginInitializationException ex(__FILE__, __LINE__, ce.reason); + throw ex; + } + + string caDir = properties->getPropertyWithDefault(propPrefix + "CertAuthDir", defaultDir); + if(!caDir.empty()) + { + CFMutableArrayRef certificateAuthorities; + if(_certificateAuthorities) + { + certificateAuthorities = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, _certificateAuthorities); + CFRelease(_certificateAuthorities); + } + else + { + certificateAuthorities = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + } + + vector<string> files = dir(caDir); + for(vector<string>::const_iterator i = files.begin(); i != files.end(); ++i) + { + try + { + CFArrayRef tmp = loadCACertificates(caDir + "/" + *i); + + CFArrayAppendArray(certificateAuthorities, tmp, CFRangeMake(0, CFArrayGetCount(tmp))); + CFRelease(tmp); + } + catch(const CertificateReadException&) + { + // + // Some files in CertAuthDir might not be certificates, we just ignore those files. + // + } + } + _certificateAuthorities = certificateAuthorities; + } + } + + // + // Import the application certificate and private keys into the application + // keychain. + // + { + string certFile = properties->getProperty(propPrefix + "CertFile"); + string keyFile = properties->getProperty(propPrefix + "KeyFile"); + vector<string>::size_type numCerts = 0; + + CFDataRef hash = 0; + + if(!certFile.empty()) + { + try + { + vector<string> files; + if(!IceUtilInternal::splitString(certFile, IceUtilInternal::pathsep, files) || files.size() > 2) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: invalid value for " + propPrefix + "CertFile:\n" + certFile; + throw ex; + } + numCerts = files.size(); + for(vector<string>::iterator p = files.begin(); p != files.end(); ++p) + { + string file = *p; + if(!checkPath(file, defaultDir, false)) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: certificate file not found:\n" + file; + throw ex; + } + + loadCertificate(&_cert, &hash, keyFile.empty() ? &_key : 0, _keychain, file, + properties->getProperty(propPrefix + "Password"), passwordPrompt, + passwordRetryMax); + break; + } + } + catch(const CertificateReadException& ce) + { + PluginInitializationException ex(__FILE__, __LINE__, ce.reason); + throw ex; + } + } + + if(!keyFile.empty()) + { + try + { + vector<string> files; + if(!IceUtilInternal::splitString(keyFile, IceUtilInternal::pathsep, files) || files.size() > 2) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: invalid value for " + propPrefix + "KeyFile:\n" + keyFile; + throw ex; + } + if(files.size() != numCerts) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: " + propPrefix + "KeyFile does not agree with " + propPrefix + "CertFile"; + throw ex; + } + for(vector<string>::iterator p = files.begin(); p != files.end(); ++p) + { + string file = *p; + if(!checkPath(file, defaultDir, false)) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: key file not found:\n" + file; + throw ex; + } + // + // The private key may be stored in an encrypted file, so handle + // password retries. + // + loadPrivateKey(&_key, keyLabel(_cert), hash, _keychain, file, + properties->getProperty(propPrefix + "Password"), + passwordPrompt, passwordRetryMax); + break; + } + } + catch(const CertificateReadException& ce) + { + PluginInitializationException ex(__FILE__, __LINE__, ce.reason); + throw ex; + } + } + + if(_cert) + { + err = SecIdentityCreateWithCertificate(_keychain, _cert, &_identity); + if(err != noErr) + { + ostringstream os; + os << "IceSSL: unable to create the certificate identity:\n" << errorToString(err); + PluginInitializationException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + } + + if(hash) + { + CFRelease(hash); + } + } + + // + // DiffieHellmanParams in DER format. + // + { + string dhParams = properties->getProperty(propPrefix + "DHParams"); + if(!dhParams.empty()) + { + if(!checkPath(dhParams, defaultDir, false)) + { + PluginInitializationException ex(__FILE__, __LINE__); + ex.reason = "IceSSL: DH params file not found:\n" + dhParams; + throw ex; + } + + ScopedArray<char> buffer; + _dhParamsLength = readFile(dhParams, buffer); + _dhParams.reset(new ScopedArray<char>(buffer)); + } + } + + // + // Establish the cipher list. + // + string ciphers = properties->getProperty(propPrefix + "Ciphers"); + if(!ciphers.empty()) + { + // + // Context used to get the cipher list + // + _ctx = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide, kSSLStreamType); + CiphersHelper::initialize(); + parseCiphers(ciphers); + } + + // + // Parse protocols + // + const string protocolVersionMax = properties->getProperty(propPrefix + "ProtocolVersionMax"); + if(!protocolVersionMax.empty()) + { + _protocolVersionMax = parseProtocol(protocolVersionMax); + } + + const string protocolVersionMin = properties->getProperty(propPrefix + "ProtocolVersionMin"); + if(!protocolVersionMin.empty()) + { + _protocolVersionMin = parseProtocol(protocolVersionMin); + } + _initialized = true; +} + +// +// Destroy the engine. +// +void +IceSSL::SecureTransportEngine::destroy() +{ + if(_certificateAuthorities) + { + CFRelease(_certificateAuthorities); + _certificateAuthorities = 0; + } + + if(_identity) + { + CFRelease(_identity); + _identity = 0; + } + + if(_cert) + { + CFRelease(_cert); + _cert = 0; + } + + if(_key) + { + CFRelease(_key); + _key = 0; + } + + if(_keychain) + { + CFRelease(_keychain); + _keychain = 0; + } + + if(_ctx) + { + CFRelease(_ctx); + _ctx = 0; + } +} + +ContextRef +IceSSL::SecureTransportEngine::newContext(bool incoming) +{ + ContextRef ssl = SSLCreateContext(kCFAllocatorDefault, incoming ? kSSLServerSide : kSSLClientSide, kSSLStreamType); + if(!ssl) + { + PluginInitializationException ex(__FILE__, __LINE__, "IceSSL: unable to create SSL context"); + throw ex; + } + + OSStatus err = noErr; + if(incoming) + { + switch(getVerifyPeer()) + { + case 0: + { + SSLSetClientSideAuthenticate(ssl, kNeverAuthenticate); + break; + } + case 1: + { + SSLSetClientSideAuthenticate(ssl, kTryAuthenticate); + break; + } + case 2: + { + SSLSetClientSideAuthenticate(ssl, kAlwaysAuthenticate); + break; + } + default: + { + assert(false); + break; + } + } + + if(_dhParamsLength > 0) + { + err = SSLSetDiffieHellmanParams(ssl, _dhParams->get(), _dhParamsLength); + if(err != noErr) + { + ostringstream os; + os << "IceSSL: unable to create the trust object:\n" << errorToString(err); + PluginInitializationException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + } + } + + if(_cert) + { + // + // Retrieve the certificate chain + // + SecPolicyRef policy = SecPolicyCreateSSL(true, 0); + SecTrustRef trust; + err = SecTrustCreateWithCertificates((CFArrayRef)_cert, policy, &trust); + if(err != noErr || !trust) + { + ostringstream os; + os << "IceSSL: unable to create the trust object"; + if(err != noErr) + { + os << '\n' << errorToString(err); + } + PluginInitializationException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + err = SecTrustSetAnchorCertificates(trust, _certificateAuthorities); + if(err != noErr) + { + ostringstream os; + os << "IceSSL: unable to establish the anchor certificates\n" << errorToString(err); + PluginInitializationException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + SecTrustResultType trustResult; + err = SecTrustEvaluate(trust, &trustResult); + if(err != noErr) + { + ostringstream os; + os << "IceSSL: unable to evaluate trust:\n" << errorToString(err); + PluginInitializationException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + int chainLength = SecTrustGetCertificateCount(trust); + CFMutableArrayRef chain = CFArrayCreateMutable(kCFAllocatorDefault, chainLength, &kCFTypeArrayCallBacks); + CFArrayAppendValue(chain, _identity); + for(int i = 1; i < chainLength; ++i) + { + CFArrayAppendValue(chain, SecTrustGetCertificateAtIndex(trust, i)); + } + CFRelease(trust); + + err = SSLSetCertificate(ssl, chain); + + CFRelease(chain); + + if(err != noErr) + { + ostringstream os; + os << "IceSSL: unable to set the SSL context certificate identity:\n" << errorToString(err); + PluginInitializationException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + } + + + if(_numCiphers != -1) + { + err = SSLSetEnabledCiphers(ssl, _ciphers->get(), _numCiphers); + if(err != noErr) + { + ostringstream os; + os << "IceSSL: failed to set enabled ciphers:\n" << errorToString(err); + PluginInitializationException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + } + err = SSLSetSessionOption(ssl, incoming ? kSSLSessionOptionBreakOnClientAuth : kSSLSessionOptionBreakOnServerAuth, + true); + + if(err != noErr) + { + ostringstream os; + os << "IceSSL: failed to set SSL option:\n" << errorToString(err); + PluginInitializationException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + if(_protocolVersionMax != kSSLProtocolUnknown) + { + err = SSLSetProtocolVersionMax(ssl, _protocolVersionMax); + if(err != noErr) + { + ostringstream os; + os << "IceSSL: failed to set SSL protocol version max:\n" << errorToString(err); + PluginInitializationException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + } + + if(_protocolVersionMin != kSSLProtocolUnknown) + { + err = SSLSetProtocolVersionMin(ssl, _protocolVersionMin); + if(err != noErr) + { + ostringstream os; + os << "IceSSL: failed to set SSL protocol version min:\n" << errorToString(err); + PluginInitializationException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + } + + return ssl; +} + +CFArrayRef +IceSSL::SecureTransportEngine::getCertificateAuthorities() const +{ + return _certificateAuthorities; +} + +string +IceSSL::SecureTransportEngine::getCipherName(SSLCipherSuite cipher) const +{ + return CiphersHelper::cipherName(cipher); +} + +void +IceSSL::SecureTransportEngine::parseCiphers(const string& ciphers) +{ + vector<string> tokens; + vector<CipherExpression> cipherExpressions; + + IceUtilInternal::splitString(ciphers, " \t", tokens); + for(vector<string>::const_iterator i = tokens.begin(); i != tokens.end(); ++i) + { + string token(*i); + if(token == "ALL") + { + if(i != tokens.begin()) + { + ostringstream os; + os << "IceSSL: `ALL' must be first in cipher list `" << ciphers << "'"; + PluginInitializationException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + _allCiphers = true; + } + else + { + CipherExpression ce; + if(token.find('!') == 0) + { + ce.negation = true; + if(token.size() > 1) + { + token = token.substr(1); + } + else + { + ostringstream os; + os << "IceSSL: invalid cipher expression `" << token << "'"; + PluginInitializationException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + } + + if(token.find('(') == 0) + { + if(token.rfind(')') != token.size() - 1) + { + ostringstream os; + os << "IceSSL: invalid cipher expression `" << token << "'"; + PluginInitializationException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + try + { + ce.re = new RegExp(token.substr(1, token.size() - 2)); + } + catch(const Ice::SyscallException&) + { + ostringstream os; + os << "IceSSL: invalid cipher expression `" << token << "'"; + PluginInitializationException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + } + else + { + ce.cipher = token; + } + + cipherExpressions.push_back(ce); + } + } + size_t numSupportedCiphers = 0; + SSLGetNumberSupportedCiphers(_ctx, &numSupportedCiphers); + + ScopedArray<SSLCipherSuite> buffer(new SSLCipherSuite[numSupportedCiphers]); + + OSStatus err; + if((err = SSLGetSupportedCiphers(_ctx, buffer.get(), &numSupportedCiphers)) != noErr) + { + ostringstream os; + os << "IceSSL: unable to get supported ciphers list (error = " << err << ")"; + PluginInitializationException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + SSLCipherSuite* supported = buffer.get(); + vector<SSLCipherSuite> allCiphers; + if(_allCiphers) + { + for(int i = 0; i < numSupportedCiphers; ++i) + { + allCiphers.push_back(supported[i]); + } + } + + for(vector<CipherExpression>::const_iterator i = cipherExpressions.begin(); i != cipherExpressions.end(); ++i) + { + CipherExpression ce = *i; + if(ce.negation) + { + for(vector<SSLCipherSuite>::iterator j = allCiphers.begin(); j != allCiphers.end();) + { + SSLCipherSuite cipher = *j; + string name = CiphersHelper::cipherName(cipher); + + if(ce.cipher.empty()) + { + if(ce.re->match(name)) + { + j = allCiphers.erase(j); + continue; + } + } + else + { + if(ce.cipher == name) + { + j = allCiphers.erase(j); + continue; + } + } + j++; + } + } + else + { + if(ce.cipher.empty()) + { + for(int i = 0; i < numSupportedCiphers; ++i) + { + SSLCipherSuite cipher = supported[i]; + string name = CiphersHelper::cipherName(cipher); + if(ce.re->match(name)) + { + vector<SSLCipherSuite>::const_iterator k = find(allCiphers.begin(), allCiphers.end(), cipher); + if(k == allCiphers.end()) + { + allCiphers.push_back(cipher); + } + } + } + } + else + { + SSLCipherSuite cipher = CiphersHelper::cipherForName(ce.cipher); + vector<SSLCipherSuite>::const_iterator k = find(allCiphers.begin(), allCiphers.end(), cipher); + if(k == allCiphers.end()) + { + allCiphers.push_back(cipher); + } + } + } + } + + if(!allCiphers.empty()) + { + _ciphers.reset(new ScopedArray<SSLCipherSuite>(new SSLCipherSuite[allCiphers.size()])); + SSLCipherSuite* enabled = _ciphers->get(); + for(vector<SSLCipherSuite>::const_iterator i = allCiphers.begin(); i != allCiphers.end(); ++i) + { + *(enabled++) = *i; + } + } + _numCiphers = allCiphers.size(); +} + +SecCertificateRef +IceSSL::SecureTransportEngine::getCertificate() const +{ + return _cert; +} + +SecKeychainRef +IceSSL::SecureTransportEngine::getKeychain() const +{ + return _keychain; +} + +#endif diff --git a/cpp/src/IceSSL/SecureTransportTransceiverI.cpp b/cpp/src/IceSSL/SecureTransportTransceiverI.cpp new file mode 100644 index 00000000000..38572c5ba65 --- /dev/null +++ b/cpp/src/IceSSL/SecureTransportTransceiverI.cpp @@ -0,0 +1,1055 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2014 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/SecureTransportTransceiverI.h> +#include <IceUtil/FileUtil.h> +#include <IceUtil/Mutex.h> +#include <IceUtil/MutexPtrLock.h> + +#include <IceSSL/ConnectionInfo.h> +#include <IceSSL/Instance.h> +#include <IceSSL/Util.h> +#include <Ice/Communicator.h> +#include <Ice/LoggerUtil.h> +#include <Ice/Buffer.h> +#include <Ice/LocalException.h> + +#ifdef ICE_USE_SECURE_TRANSPORT + +using namespace std; +using namespace Ice; +using namespace IceSSL; + +namespace +{ + +string +trustResultDescription(SecTrustResultType result) +{ + switch(result) + { + case kSecTrustResultInvalid: + { + return "Invalid setting or result."; + } + case kSecTrustResultDeny: + { + return "The user specified that the certificate should not be trusted."; + } + case kSecTrustResultRecoverableTrustFailure: + { + return "Trust denied; retry after changing settings."; + } + case kSecTrustResultFatalTrustFailure: + { + return "Trust denied; no simple fix is available."; + } + case kSecTrustResultOtherError: + { + return "A failure other than that of trust evaluation."; + } + default: + { + assert(false); + return ""; + } + } +} + +string +protocolName(SSLProtocol protocol) +{ + switch(protocol) + { + case kSSLProtocol2: + return "SSL 2.0"; + case kSSLProtocol3: + return "SSL 3.0"; + case kTLSProtocol1: + return "TLS 1.0"; + case kTLSProtocol11: + return "TLS 1.1"; + case kTLSProtocol12: + return "TLS 1.2"; + default: + return "Unknown"; + } +} + +// +// Socket write callback +// +OSStatus +socketWrite(SSLConnectionRef connection, const void* data, size_t* length) +{ + const TransceiverI* transceiver = static_cast<const TransceiverI*>(connection); + assert(transceiver); + return transceiver->writeRaw(reinterpret_cast<const char*>(data), length); +} + +// +// Socket read callback +// +OSStatus +socketRead(SSLConnectionRef connection, void* data, size_t* length) +{ + const TransceiverI* transceiver = static_cast<const TransceiverI*>(connection); + assert(transceiver); + return transceiver->readRaw(reinterpret_cast<char*>(data), length); +} + +} + +IceInternal::NativeInfoPtr +IceSSL::TransceiverI::getNativeInfo() +{ + return this; +} + +IceInternal::SocketOperation +IceSSL::TransceiverI::initialize(IceInternal::Buffer& readBuffer, IceInternal::Buffer& writeBuffer, bool&) +{ + try + { + if(_state == StateNeedConnect) + { + _state = StateConnectPending; + return IceInternal::SocketOperationConnect; + } + else if(_state <= StateConnectPending) + { + IceInternal::doFinishConnect(_fd); + _desc = IceInternal::fdToString(_fd, _proxy, _addr, true); + + if(_proxy) + { + // + // Prepare the read & write buffers in advance. + // + _proxy->beginWriteConnectRequest(_addr, writeBuffer); + _proxy->beginReadConnectRequestResponse(readBuffer); + + // + // Write the proxy connection message using TCP. + // + if(writeRaw(writeBuffer)) + { + // + // Write completed without blocking. + // + _proxy->endWriteConnectRequest(writeBuffer); + + // + // Try to read the response using TCP. + // + if(readRaw(readBuffer)) + { + // + // Read completed without blocking - fall through. + // + _proxy->endReadConnectRequestResponse(readBuffer); + } + else + { + // + // Return SocketOperationRead to indicate we need to complete the read. + // + _state = StateProxyConnectRequestPending; // Wait for proxy response + return IceInternal::SocketOperationRead; + } + } + else + { + // + // Return SocketOperationWrite to indicate we need to complete the write. + // + _state = StateProxyConnectRequest; // Send proxy connect request + return IceInternal::SocketOperationWrite; + } + } + + _state = StateConnected; + } + else if(_state == StateProxyConnectRequest) + { + // + // Write completed. + // + _proxy->endWriteConnectRequest(writeBuffer); + _state = StateProxyConnectRequestPending; // Wait for proxy response + return IceInternal::SocketOperationRead; + } + else if(_state == StateProxyConnectRequestPending) + { + // + // Read completed. + // + _proxy->endReadConnectRequestResponse(readBuffer); + _state = StateConnected; + } + + assert(_state == StateConnected); + + OSStatus err = noErr; + + if(!_ssl) + { + // + // Initialize SSL context + // + _ssl = _engine->newContext(_incoming); + if((err = SSLSetIOFuncs(_ssl, socketRead, socketWrite)) != noErr) + { + ostringstream os; + os << "IceSSL: cannot set SSL IO functions\n" << errorToString(err); + throw PluginInitializationException(__FILE__, __LINE__, os.str()); + } + + if((err = SSLSetConnection(_ssl, reinterpret_cast<SSLConnectionRef>(this))) != noErr) + { + ostringstream os; + os << "IceSSL: cannot set SSL connection\n" << errorToString(err); + throw PluginInitializationException(__FILE__, __LINE__, os.str()); + } + } + + SSLSessionState state; + SSLGetSessionState(_ssl, &state); + + // + // SSL Handshake + // + while(state == kSSLHandshake || state == kSSLIdle) + { + err = SSLHandshake(_ssl); + if(err != noErr) + { + switch(err) + { + case errSSLWouldBlock: + { + assert(_flags & SSLWantRead || _flags & SSLWantWrite); + return _flags & SSLWantRead ? IceInternal::SocketOperationRead : IceInternal::SocketOperationWrite; + } + case errSSLPeerAuthCompleted: + { + assert(!_trust); + err = SSLCopyPeerTrust(_ssl, &_trust); + if(err != noErr) + { + break; + } + + SecTrustResultType trustResult = kSecTrustResultOtherError; + + if(_trust) + { + err = SecTrustSetAnchorCertificates(_trust, _engine->getCertificateAuthorities()); + + if(err != noErr) + { + ostringstream os; + os << "SSL handsake failure:\n" << errorToString(err); + throw SecurityException(__FILE__, __LINE__, os.str()); + } + + // + // Disable network fetch, we don't want this to block. + // + err = SecTrustSetNetworkFetchAllowed(_trust, false); + if(err != noErr) + { + ostringstream os; + os << "SSL handsake failure:\n" << errorToString(err); + throw SecurityException(__FILE__, __LINE__, os.str()); + } + + // + // Evaluate the trust + // + err = SecTrustEvaluate(_trust, &trustResult); + if(err != noErr) + { + ostringstream os; + os << "SSL handsake failure:\n" << errorToString(err); + throw SecurityException(__FILE__, __LINE__, os.str());; + } + } + + switch(trustResult) + { + case kSecTrustResultUnspecified: + case kSecTrustResultProceed: + { + // + // Trust verify success. + // + break; + } + case kSecTrustResultInvalid: + //case kSecTrustResultConfirm: // Used in old OS X versions + case kSecTrustResultDeny: + case kSecTrustResultRecoverableTrustFailure: + case kSecTrustResultFatalTrustFailure: + case kSecTrustResultOtherError: + { + if(_engine->getVerifyPeer() == 0) + { + if(_instance->traceLevel() >= 1) + { + ostringstream os; + os << "IceSSL: ignoring certificate verification failure\n" + << trustResultDescription(trustResult); + _instance->logger()->trace(_instance->traceCategory(), os.str()); + } + break; + } + else + { + ostringstream os; + os << "IceSSL: certificate verification failure\n" + << trustResultDescription(trustResult); + string msg = os.str(); + if(_instance->traceLevel() >= 1) + { + _instance->logger()->trace(_instance->traceCategory(), msg); + } + throw ProtocolException(__FILE__, __LINE__, msg); + } + } + } + // + // Call SSLHandshake to resume the handsake. + // + continue; + } + default: + { + break; + } + } + + if(err == errSSLClosedGraceful || err == errSSLClosedAbort) + { + ConnectionLostException ex(__FILE__, __LINE__); + ex.error = 0; + throw ex; + } + else + { + IceInternal::Address remoteAddr; + string desc = "<not available>"; + if(IceInternal::fdToRemoteAddress(_fd, remoteAddr)) + { + desc = IceInternal::addrToString(remoteAddr); + } + ostringstream os; + os << "SSL error occurred for new " << (_incoming ? "incoming" : "outgoing") + << " connection:\nremote address = " << desc << "\n" + << errorToString(err); + ProtocolException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + } + break; + } + _engine->verifyPeer(_fd, _host, getNativeConnectionInfo()); + _state = StateHandshakeComplete; + } + catch(const Ice::LocalException& ex) + { + if(_instance->traceLevel() >= 2) + { + Trace out(_instance->logger(), _instance->traceCategory()); + out << "failed to establish " << _instance->protocol() << " connection\n"; + if(_incoming) + { + out << IceInternal::fdToString(_fd) << "\n" << ex; + } + else + { + out << IceInternal::fdToString(_fd, _proxy, _addr, false) << "\n" << ex; + } + } + throw; + } + + if(_instance->traceLevel() >= 1) + { + Trace out(_instance->logger(), _instance->traceCategory()); + if(_incoming) + { + out << "accepted " << _instance->protocol() << " connection\n" << _desc; + } + else + { + out << _instance->protocol() << " connection established\n" << _desc; + } + } + + if(_instance->securityTraceLevel() >= 1) + { + traceConnection(); + } + + return IceInternal::SocketOperationNone; +} + +IceInternal::SocketOperation +IceSSL::TransceiverI::closing(bool initiator, const Ice::LocalException&) +{ + // If we are initiating the connection closure, wait for the peer + // to close the TCP/IP connection. Otherwise, close immediately. + return initiator ? IceInternal::SocketOperationRead : IceInternal::SocketOperationNone; +} + +void +IceSSL::TransceiverI::close() +{ + if(_state == StateHandshakeComplete && _instance->traceLevel() >= 1) + { + Trace out(_instance->logger(), _instance->traceCategory()); + out << "closing " << _instance->protocol() << " connection\n" << toString(); + } + + if(_trust) + { + CFRelease(_trust); + _trust = 0; + } + + if(_ssl) + { + SSLClose(_ssl); + CFRelease(_ssl); + _ssl = 0; + } + + assert(_fd != INVALID_SOCKET); + try + { + IceInternal::closeSocket(_fd); + _fd = INVALID_SOCKET; + } + catch(const SocketException&) + { + _fd = INVALID_SOCKET; + throw; + } +} + +IceInternal::SocketOperation +IceSSL::TransceiverI::write(IceInternal::Buffer& buf) +{ + if(_state == StateProxyConnectRequest) + { + // + // We need to write the proxy message, but we have to use TCP and not SSL. + // + return writeRaw(buf) ? IceInternal::SocketOperationNone : IceInternal::SocketOperationWrite; + } + + size_t processed = 0; + + if(_buffered > 0) + { + // + // Required to flush SSL buffers + // + if(SSLWrite(_ssl, 0, 0, &processed) == errSSLWouldBlock) + { + return IceInternal::SocketOperationWrite; + } + + buf.i += _buffered; + _buffered = 0; + } + + if(buf.i == buf.b.end()) + { + return IceInternal::SocketOperationNone; + } + + // + // It's impossible for packetSize to be more than an Int. + // + size_t packetSize = static_cast<size_t>(buf.b.end() - buf.i); + packetSize = std::min(packetSize, _maxSendPacketSize); + while(buf.i != buf.b.end()) + { + assert(_fd != INVALID_SOCKET); + OSStatus ret = SSLWrite(_ssl, reinterpret_cast<const void*>(buf.i), packetSize, &processed); + if(ret != noErr) + { + if(ret == errSSLWouldBlock) + { + _buffered = processed; + assert(_flags & SSLWantWrite); + return IceInternal::SocketOperationWrite; + } + + if(ret == errSSLClosedGraceful) + { + ConnectionLostException ex(__FILE__, __LINE__); + ex.error = 0; + throw ex; + } + + // + // SSL protocol errors are defined in SecureTransport.h are in the range + // -9800 to -9849 + // + if(ret <= -9800 && ret >= -9849) + { + ProtocolException ex(__FILE__, __LINE__); + ostringstream os; + os << "SSL protocol error during read:\n" << errorToString(ret); + ex.reason = os.str(); + throw ex; + } + + errno = ret; + if(IceInternal::connectionLost()) + { + ConnectionLostException ex(__FILE__, __LINE__); + ex.error = IceInternal::getSocketErrno(); + throw ex; + } + else + { + SocketException ex(__FILE__, __LINE__); + ex.error = IceInternal::getSocketErrno(); + throw ex; + } + } + + if(_instance->traceLevel() >= 3) + { + Trace out(_instance->logger(), _instance->traceCategory()); + out << "sent " << processed << " of " << packetSize << " bytes via " << protocol() << "\n" << toString(); + } + + buf.i += processed; + + if(packetSize > buf.b.end() - buf.i) + { + packetSize = static_cast<int>(buf.b.end() - buf.i); + } + } + + return IceInternal::SocketOperationNone; +} + +IceInternal::SocketOperation +IceSSL::TransceiverI::read(IceInternal::Buffer& buf, bool&) +{ + if(_state == StateProxyConnectRequestPending) + { + // + // We need to read the proxy reply, but we have to use TCP and not SSL. + // + return readRaw(buf) ? IceInternal::SocketOperationNone : IceInternal::SocketOperationRead; + } + + if(buf.i == buf.b.end()) + { + return IceInternal::SocketOperationNone; + } + // + // It's impossible for packetSize to be more than an Int. + // + size_t packetSize = static_cast<int>(buf.b.end() - buf.i); + size_t processed = 0; + + packetSize = std::min(packetSize, _maxReceivePacketSize); + + while(buf.i != buf.b.end()) + { + assert(_fd != INVALID_SOCKET); + OSStatus ret = SSLRead(_ssl, reinterpret_cast<void*>(buf.i), packetSize, &processed); + if(ret != noErr) + { + if(ret == errSSLWouldBlock) + { + buf.i += processed; + assert(_flags & SSLWantRead); + return IceInternal::SocketOperationRead; + } + + if(ret == errSSLClosedGraceful || ret == errSSLPeerBadRecordMac || ret == errSSLPeerDecryptionFail) + { + // + // Forcefully closing a connection can result in SSLRead reporting + // "decryption failed or bad record mac". We trap that error and + // treat it as the loss of a connection. + // + ConnectionLostException ex(__FILE__, __LINE__); + ex.error = 0; + throw ex; + } + + // + // SSL protocol errors are defined in SecureTransport.h are in the range + // -9800 to -9849 + // + if(ret <= -9800 && ret >= -9849) + { + ProtocolException ex(__FILE__, __LINE__); + ostringstream os; + os << "SSL protocol error during read:\n" << errorToString(ret); + ex.reason = os.str(); + throw ex; + } + + errno = ret; + if(IceInternal::connectionLost()) + { + ConnectionLostException ex(__FILE__, __LINE__); + ex.error = IceInternal::getSocketErrno(); + throw ex; + } + else + { + SocketException ex(__FILE__, __LINE__); + ex.error = IceInternal::getSocketErrno(); + throw ex; + } + } + + if(_instance->traceLevel() >= 3) + { + Trace out(_instance->logger(), _instance->traceCategory()); + out << "received " << processed << " of " << packetSize << " bytes via " << protocol() << "\n" + << toString(); + } + buf.i += processed; + + if(packetSize > buf.b.end() - buf.i) + { + packetSize = static_cast<int>(buf.b.end() - buf.i); + } + } + return IceInternal::SocketOperationNone; +} + +string +IceSSL::TransceiverI::protocol() const +{ + return _instance->protocol(); +} + +string +IceSSL::TransceiverI::toString() const +{ + return _desc; +} + +Ice::ConnectionInfoPtr +IceSSL::TransceiverI::getInfo() const +{ + return getNativeConnectionInfo(); +} + +void +IceSSL::TransceiverI::checkSendSize(const IceInternal::Buffer& buf, size_t messageSizeMax) +{ + if(buf.b.size() > messageSizeMax) + { + IceInternal::Ex::throwMemoryLimitException(__FILE__, __LINE__, buf.b.size(), messageSizeMax); + } +} + +SecTrustRef +IceSSL::TransceiverI::trust() const +{ + return _trust; +} + +ContextRef +IceSSL::TransceiverI::context() const +{ + return _ssl; +} + +IceSSL::TransceiverI::TransceiverI(const InstancePtr& instance, SOCKET fd, const IceInternal::NetworkProxyPtr& proxy, + const string& host, const IceInternal::Address& addr) : + IceInternal::NativeInfo(fd), + _instance(instance), + _engine(SecureTransportEnginePtr::dynamicCast(instance->engine())), + _proxy(proxy), + _host(host), + _addr(addr), + _incoming(false), + _ssl(0), + _trust(0), + _buffered(0), + _state(StateNeedConnect) +{ + assert(_engine); + IceInternal::setBlock(fd, false); + IceInternal::setTcpBufSize(fd, _instance->properties(), _instance->logger()); + + IceInternal::Address connectAddr = proxy ? proxy->getAddress() : addr; + if(IceInternal::doConnect(_fd, connectAddr)) + { + _state = StateConnected; + _desc = IceInternal::fdToString(_fd, _proxy, _addr, true); + if(_instance->traceLevel() >= 1) + { + Trace out(_instance->logger(), _instance->traceCategory()); + out << _instance->protocol() << " connection established\n" << _desc; + } + } + else + { + _desc = IceInternal::fdToString(_fd, _proxy, _addr, true); + } + + // + // Limit the size of packet pass to SSLWrite/SSLRead to avoid blocking and + // holding too much memory. + // + _maxSendPacketSize = std::min(512, IceInternal::getSendBufferSize(fd)); + _maxReceivePacketSize = std::min(512, IceInternal::getRecvBufferSize(fd)); +} + +IceSSL::TransceiverI::TransceiverI(const InstancePtr& instance, SOCKET fd, const string& adapterName) : + IceInternal::NativeInfo(fd), + _instance(instance), + _engine(SecureTransportEnginePtr::dynamicCast(instance->engine())), + _addr(IceInternal::Address()), + _adapterName(adapterName), + _incoming(true), + _ssl(0), + _trust(0), + _buffered(0), + _state(StateConnected), + _desc(IceInternal::fdToString(fd)) +{ + assert(_engine); + IceInternal::setBlock(fd, false); + IceInternal::setTcpBufSize(fd, _instance->properties(), _instance->logger()); + + // + // Limit the size of packet pass to SSLWrite/SSLRead to avoid blocking and + // holding too much memory. + // + _maxSendPacketSize = std::min(512, IceInternal::getSendBufferSize(fd)); + _maxReceivePacketSize = std::min(512, IceInternal::getRecvBufferSize(fd)); +} + +IceSSL::TransceiverI::~TransceiverI() +{ + assert(_fd == INVALID_SOCKET); +} + +NativeConnectionInfoPtr +IceSSL::TransceiverI::getNativeConnectionInfo() const +{ + NativeConnectionInfoPtr info = new NativeConnectionInfo(); + IceInternal::fdToAddressAndPort(_fd, info->localAddress, info->localPort, info->remoteAddress, info->remotePort); + + if(_ssl) + { + for(int i = 0, count = SecTrustGetCertificateCount(_trust); i < count; ++i) + { + SecCertificateRef cert = SecTrustGetCertificateAtIndex(_trust, i); + CFRetain(cert); + + CertificatePtr certificate = new Certificate(cert); + info->nativeCerts.push_back(certificate); + info->certs.push_back(certificate->encode()); + } + + SSLCipherSuite cipher; + SSLGetNegotiatedCipher(_ssl, &cipher); + info->cipher = _engine->getCipherName(cipher); + } + + info->adapterName = _adapterName; + info->incoming = _incoming; + return info; +} + +bool +IceSSL::TransceiverI::writeRaw(IceInternal::Buffer& buf) +{ + // + // It's impossible for packetSize to be more than an Int. + // + int packetSize = static_cast<int>(buf.b.end() - buf.i); + while(buf.i != buf.b.end()) + { + assert(_fd != INVALID_SOCKET); + + ssize_t ret = ::send(_fd, reinterpret_cast<const char*>(&*buf.i), packetSize, 0); + if(ret == 0) + { + ConnectionLostException ex(__FILE__, __LINE__); + ex.error = 0; + throw ex; + } + + if(ret == SOCKET_ERROR) + { + if(IceInternal::interrupted()) + { + continue; + } + + if(IceInternal::noBuffers() && packetSize > 1024) + { + packetSize /= 2; + continue; + } + + if(IceInternal::wouldBlock()) + { + return false; + } + + if(IceInternal::connectionLost()) + { + ConnectionLostException ex(__FILE__, __LINE__); + ex.error = IceInternal::getSocketErrno(); + throw ex; + } + else + { + SocketException ex(__FILE__, __LINE__); + ex.error = IceInternal::getSocketErrno(); + throw ex; + } + } + + if(_instance->traceLevel() >= 3) + { + Trace out(_instance->logger(), _instance->traceCategory()); + out << "sent " << ret << " of " << packetSize << " bytes via " << protocol() << "\n" << toString(); + } + + buf.i += ret; + + if(packetSize > buf.b.end() - buf.i) + { + packetSize = static_cast<int>(buf.b.end() - buf.i); + } + } + + return true; +} + +bool +IceSSL::TransceiverI::readRaw(IceInternal::Buffer& buf) +{ + // + // It's impossible for packetSize to be more than an Int. + // + int packetSize = static_cast<int>(buf.b.end() - buf.i); + while(buf.i != buf.b.end()) + { + assert(_fd != INVALID_SOCKET); + ssize_t ret = ::recv(_fd, reinterpret_cast<char*>(&*buf.i), packetSize, 0); + + if(ret == 0) + { + ConnectionLostException ex(__FILE__, __LINE__); + ex.error = 0; + throw ex; + } + + if(ret == SOCKET_ERROR) + { + if(IceInternal::interrupted()) + { + continue; + } + + if(IceInternal::noBuffers() && packetSize > 1024) + { + packetSize /= 2; + continue; + } + + if(IceInternal::wouldBlock()) + { + return false; + } + + if(IceInternal::connectionLost()) + { + ConnectionLostException ex(__FILE__, __LINE__); + ex.error = IceInternal::getSocketErrno(); + throw ex; + } + else + { + SocketException ex(__FILE__, __LINE__); + ex.error = IceInternal::getSocketErrno(); + throw ex; + } + } + + if(_instance->traceLevel() >= 3) + { + Trace out(_instance->logger(), _instance->traceCategory()); + out << "received " << ret << " of " << packetSize << " bytes via " << protocol() << "\n" << toString(); + } + + buf.i += ret; + + packetSize = static_cast<int>(buf.b.end() - buf.i); + } + + return true; +} + +// +// Trace connection +// +void +IceSSL::TransceiverI::traceConnection() +{ + assert(_ssl); + Trace out(_instance->logger(), _instance->traceCategory()); + out << "SSL summary for " << (_incoming ? "incoming" : "outgoing") << " connection\n"; + + SSLProtocol protocol; + SSLGetNegotiatedProtocolVersion(_ssl, &protocol); + const string sslProtocolName = protocolName(protocol); + + SSLCipherSuite cipher; + SSLGetNegotiatedCipher(_ssl, &cipher); + const string sslCipherName = _engine->getCipherName(cipher); + + if(sslCipherName.empty()) + { + out << "unknown cipher\n"; + } + else + { + out << "cipher = " << sslCipherName << "\n"; + out << "protocol = " << sslProtocolName << "\n"; + } + out << IceInternal::fdToString(_fd); +} + +OSStatus +IceSSL::TransceiverI::writeRaw(const char* data, size_t* length) const +{ + _flags &= ~SSLWantWrite; + + assert(_fd != INVALID_SOCKET); + + char* i = const_cast<char*>(data); + int packetSize = *length; + char* end = i + packetSize; + + while(i != end) + { + ssize_t ret = ::send(_fd, const_cast<const char*>(i), packetSize, 0); + if(ret == 0) + { + return errSSLClosedGraceful; + } + + if(ret == SOCKET_ERROR) + { + if(IceInternal::interrupted()) + { + continue; + } + + if(IceInternal::noBuffers() && packetSize > 1024) + { + packetSize /= 2; + continue; + } + + if(IceInternal::wouldBlock()) + { + *length = static_cast<int>(i - data); + _flags |= SSLWantWrite; + return errSSLWouldBlock; + } + return errno; + } + + i += ret; + if(_instance->traceLevel() >= 3) + { + Trace out(_instance->logger(), _instance->traceCategory()); + out << "sent " << ret << " of " << packetSize << " bytes via " << protocol() << "\n" << toString(); + } + + if(packetSize > end - i) + { + packetSize = static_cast<int>(end - i); + } + } + *length = static_cast<int>(i - data); + return noErr; +} + +OSStatus +IceSSL::TransceiverI::readRaw(char* data, size_t* length) const +{ + _flags &= ~SSLWantRead; + + assert(_fd != INVALID_SOCKET); + + char* i = data; + int packetSize = *length; + char* end = i + packetSize; + while(i != end) + { + ssize_t ret = ::recv(_fd, i, packetSize, 0); + if(ret == 0) + { + return errSSLClosedGraceful; + } + + if(ret == SOCKET_ERROR) + { + if(IceInternal::interrupted()) + { + continue; + } + + if(IceInternal::noBuffers() && packetSize > 1024) + { + packetSize /= 2; + continue; + } + + if(IceInternal::wouldBlock()) + { + *length = static_cast<int>(i - data); + _flags |= SSLWantRead; + return errSSLWouldBlock; + } + return errno; + } + + i += ret; + + if(_instance->traceLevel() >= 3) + { + Trace out(_instance->logger(), _instance->traceCategory()); + out << "received " << ret << " of " << packetSize << " bytes via " << protocol() << "\n" << toString(); + } + + packetSize = static_cast<int>(end - i); + } + + *length = static_cast<int>(i - data); + return noErr; +} + +#endif diff --git a/cpp/src/IceSSL/SecureTransportTransceiverI.h b/cpp/src/IceSSL/SecureTransportTransceiverI.h new file mode 100644 index 00000000000..c38c8d26b4c --- /dev/null +++ b/cpp/src/IceSSL/SecureTransportTransceiverI.h @@ -0,0 +1,114 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2014 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. +// +// ********************************************************************** + +#ifndef ICE_SSL_SECURE_TRANSPORT_TRANSCEIVER_I_H +#define ICE_SSL_SECURE_TRANSPORT_TRANSCEIVER_I_H + +#include <IceSSL/Config.h> +#include <IceSSL/InstanceF.h> +#include <IceSSL/SSLEngineF.h> +#include <IceSSL/Plugin.h> + +#include <Ice/Transceiver.h> +#include <Ice/Network.h> + +#ifdef ICE_USE_SECURE_TRANSPORT + +#include <Security/Security.h> +#include <CoreFoundation/CoreFoundation.h> + +namespace IceSSL +{ + +class ConnectorI; +class AcceptorI; + +class TransceiverI : public IceInternal::Transceiver, public IceInternal::NativeInfo +{ + enum State + { + StateNeedConnect, + StateConnectPending, + StateProxyConnectRequest, + StateProxyConnectRequestPending, + StateConnected, + StateHandshakeComplete + }; + +public: + + virtual IceInternal::NativeInfoPtr getNativeInfo(); + + virtual IceInternal::SocketOperation initialize(IceInternal::Buffer&, IceInternal::Buffer&, bool&); + virtual IceInternal::SocketOperation closing(bool, const Ice::LocalException&); + virtual void close(); + virtual IceInternal::SocketOperation write(IceInternal::Buffer&); + virtual IceInternal::SocketOperation read(IceInternal::Buffer&, bool&); + + virtual std::string protocol() const; + virtual std::string toString() const; + virtual Ice::ConnectionInfoPtr getInfo() const; + virtual void checkSendSize(const IceInternal::Buffer&, size_t); + + ContextRef context() const; + SecTrustRef trust() const; + OSStatus writeRaw(const char*, size_t*) const; + OSStatus readRaw(char*, size_t*) const; + +private: + + TransceiverI(const InstancePtr&, SOCKET, const IceInternal::NetworkProxyPtr&, const std::string&, + const IceInternal::Address&); + TransceiverI(const InstancePtr&, SOCKET, const std::string&); + virtual ~TransceiverI(); + + virtual NativeConnectionInfoPtr getNativeConnectionInfo() const; + + void traceConnection(); + + bool writeRaw(IceInternal::Buffer&); + bool readRaw(IceInternal::Buffer&); + + friend class ConnectorI; + friend class AcceptorI; + + const InstancePtr _instance; + const SecureTransportEnginePtr _engine; + + const IceInternal::NetworkProxyPtr _proxy; + const std::string _host; + const IceInternal::Address _addr; + + const std::string _adapterName; + const bool _incoming; + + ContextRef _ssl; + SecTrustRef _trust; + + size_t _buffered; + enum SSLWantFlags + { + SSLWantRead = 0x1, + SSLWantWrite = 0x2 + }; + + mutable Ice::Byte _flags; + + State _state; + std::string _desc; + size_t _maxSendPacketSize; + size_t _maxReceivePacketSize; +}; +typedef IceUtil::Handle<TransceiverI> TransceiverIPtr; + +} + +#endif + +#endif diff --git a/cpp/src/IceSSL/TransceiverI.cpp b/cpp/src/IceSSL/TransceiverI.cpp index 1e07c42e9c3..d749cdf9651 100644 --- a/cpp/src/IceSSL/TransceiverI.cpp +++ b/cpp/src/IceSSL/TransceiverI.cpp @@ -7,10 +7,11 @@ // // ********************************************************************** +#include <IceSSL/TransceiverI.h> + #include <IceUtil/Mutex.h> #include <IceUtil/MutexPtrLock.h> -#include <IceSSL/TransceiverI.h> #include <IceSSL/ConnectionInfo.h> #include <IceSSL/Instance.h> #include <IceSSL/Util.h> @@ -21,14 +22,11 @@ #include <IceUtil/DisableWarnings.h> +#ifdef ICE_USE_OPENSSL + #include <openssl/err.h> #include <openssl/bio.h> -// Ignore OS X OpenSSL deprecation warnings -#ifdef __APPLE__ -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif - using namespace std; using namespace Ice; using namespace IceSSL; @@ -231,7 +229,7 @@ IceSSL::TransceiverI::initialize(IceInternal::Buffer& readBuffer, IceInternal::B throw ex; } - _ssl = SSL_new(_instance->context()); + _ssl = SSL_new(_engine->context()); if(!_ssl) { BIO_free(bio); @@ -364,7 +362,7 @@ IceSSL::TransceiverI::initialize(IceInternal::Buffer& readBuffer, IceInternal::B } ostringstream ostr; ostr << "SSL error occurred for new " << (_incoming ? "incoming" : "outgoing") - << " connection:\nremote address = " << desc << "\n" << _instance->sslErrors(); + << " connection:\nremote address = " << desc << "\n" << _engine->sslErrors(); ProtocolException ex(__FILE__, __LINE__); ex.reason = ostr.str(); throw ex; @@ -373,7 +371,7 @@ IceSSL::TransceiverI::initialize(IceInternal::Buffer& readBuffer, IceInternal::B } } - _instance->verifyPeer(_ssl, _fd, _host, getNativeConnectionInfo()); + _engine->verifyPeer(_ssl, _fd, _host, getNativeConnectionInfo()); _state = StateHandshakeComplete; } catch(const Ice::LocalException& ex) @@ -409,7 +407,7 @@ IceSSL::TransceiverI::initialize(IceInternal::Buffer& readBuffer, IceInternal::B if(_instance->securityTraceLevel() >= 1) { - _instance->traceConnection(_ssl, _incoming); + traceConnection(); } return IceInternal::SocketOperationNone; @@ -597,7 +595,7 @@ IceSSL::TransceiverI::write(IceInternal::Buffer& buf) case SSL_ERROR_SSL: { ProtocolException ex(__FILE__, __LINE__); - ex.reason = "SSL protocol error during write:\n" + _instance->sslErrors(); + ex.reason = "SSL protocol error during write:\n" + _engine->sslErrors(); throw ex; } } @@ -770,7 +768,7 @@ IceSSL::TransceiverI::read(IceInternal::Buffer& buf, bool&) else { ProtocolException ex(__FILE__, __LINE__); - ex.reason = "SSL protocol error during read:\n" + _instance->sslErrors(); + ex.reason = "SSL protocol error during read:\n" + _engine->sslErrors(); throw ex; } } @@ -1041,6 +1039,7 @@ IceSSL::TransceiverI::TransceiverI(const InstancePtr& instance, SOCKET fd, const const string& host, const IceInternal::Address& addr) : IceInternal::NativeInfo(fd), _instance(instance), + _engine(OpenSSLEnginePtr::dynamicCast(instance->engine())), _proxy(proxy), _host(host), _addr(addr), @@ -1078,6 +1077,7 @@ IceSSL::TransceiverI::TransceiverI(const InstancePtr& instance, SOCKET fd, const IceSSL::TransceiverI::TransceiverI(const InstancePtr& instance, SOCKET fd, const string& adapterName) : IceInternal::NativeInfo(fd), _instance(instance), + _engine(OpenSSLEnginePtr::dynamicCast(instance->engine())), _addr(IceInternal::Address()), _adapterName(adapterName), _incoming(true), @@ -1366,6 +1366,30 @@ IceSSL::TransceiverI::readAsync(char* buf, int packetSize) #endif +void +IceSSL::TransceiverI::traceConnection() +{ + Trace out(_instance->logger(), _instance->traceCategory()); + out << "SSL summary for " << (_incoming ? "incoming" : "outgoing") << " connection\n"; + + // + // The const_cast is necesary because Solaris still uses OpenSSL 0.9.7. + // + //const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); + SSL_CIPHER *cipher = const_cast<SSL_CIPHER*>(SSL_get_current_cipher(_ssl)); + if(!cipher) + { + out << "unknown cipher\n"; + } + else + { + out << "cipher = " << SSL_CIPHER_get_name(cipher) << "\n"; + out << "bits = " << SSL_CIPHER_get_bits(cipher, 0) << "\n"; + out << "protocol = " << SSL_get_version(_ssl) << "\n"; + } + out << IceInternal::fdToString(SSL_get_fd(_ssl)); +} + bool IceSSL::TransceiverI::writeRaw(IceInternal::Buffer& buf) { @@ -1507,3 +1531,4 @@ IceSSL::TransceiverI::readRaw(IceInternal::Buffer& buf) return true; } +#endif diff --git a/cpp/src/IceSSL/TransceiverI.h b/cpp/src/IceSSL/TransceiverI.h index 4025da7f108..b74da103485 100644 --- a/cpp/src/IceSSL/TransceiverI.h +++ b/cpp/src/IceSSL/TransceiverI.h @@ -10,12 +10,16 @@ #ifndef ICE_SSL_TRANSCEIVER_I_H #define ICE_SSL_TRANSCEIVER_I_H +#include <IceSSL/Config.h> #include <IceSSL/InstanceF.h> #include <IceSSL/Plugin.h> +#include <IceSSL/SSLEngineF.h> #include <Ice/Transceiver.h> #include <Ice/Network.h> +#ifdef ICE_USE_OPENSSL + typedef struct ssl_st SSL; typedef struct bio_st BIO; @@ -76,6 +80,7 @@ private: int readAsync(char*, int); #endif + void traceConnection(); bool writeRaw(IceInternal::Buffer&); bool readRaw(IceInternal::Buffer&); @@ -83,7 +88,8 @@ private: friend class AcceptorI; const InstancePtr _instance; - + const OpenSSLEnginePtr _engine; + const IceInternal::NetworkProxyPtr _proxy; const std::string _host; const IceInternal::Address _addr; @@ -114,3 +120,5 @@ typedef IceUtil::Handle<TransceiverI> TransceiverIPtr; } #endif + +#endif diff --git a/cpp/src/IceSSL/Util.cpp b/cpp/src/IceSSL/Util.cpp index a46db385398..518fd9f5d67 100644 --- a/cpp/src/IceSSL/Util.cpp +++ b/cpp/src/IceSSL/Util.cpp @@ -13,33 +13,31 @@ #endif #include <IceSSL/Util.h> +#include <IceUtil/ScopedArray.h> #include <IceUtil/FileUtil.h> + #include <Ice/LocalException.h> #include <Ice/Network.h> #include <Ice/Object.h> -#ifdef _WIN32 -# include <direct.h> -# include <sys/types.h> -#endif - -#include <openssl/err.h> - -#include <IceUtil/DisableWarnings.h> - -// Ignore OS X OpenSSL deprecation warnings -#ifdef __APPLE__ -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#ifdef ICE_USE_OPENSSL +# ifdef _WIN32 +# include <direct.h> +# include <sys/types.h> +# endif +# include <openssl/err.h> #endif using namespace std; using namespace Ice; +using namespace IceUtil; using namespace IceSSL; +#ifdef ICE_USE_OPENSSL namespace { -#ifndef OPENSSL_NO_DH +# ifndef OPENSSL_NO_DH // The following arrays are predefined Diffie Hellman group parameters. // These are known strong primes, distributed with the OpenSSL library @@ -278,41 +276,7 @@ IceSSL::DHParams::get(int keyLength) } } -#endif - -bool -IceSSL::checkPath(string& path, const string& defaultDir, bool dir) -{ - // - // Check if file exists. If not, try prepending the default - // directory and check again. If the path exists, the string - // argument is modified and true is returned. Otherwise - // false is returned. - // - IceUtilInternal::structstat st; - int err = IceUtilInternal::stat(path, &st); - if(err == 0) - { - return dir ? S_ISDIR(st.st_mode) != 0 : S_ISREG(st.st_mode) != 0; - } - - if(!defaultDir.empty()) - { -#ifdef _WIN32 - string s = defaultDir + "\\" + path; -#else - string s = defaultDir + "/" + path; -#endif - err = ::IceUtilInternal::stat(s.c_str(), &st); - if(err == 0 && ((!dir && S_ISREG(st.st_mode)) || (dir && S_ISDIR(st.st_mode)))) - { - path = s; - return true; - } - } - - return false; -} +# endif string IceSSL::getSslErrors(bool verbose) @@ -368,3 +332,583 @@ IceSSL::getSslErrors(bool verbose) return ostr.str(); } + +#elif defined(ICE_USE_SECURE_TRANSPORT) + +string +IceSSL::errorToString(CFErrorRef err) +{ + ostringstream os; + if(err) + { + CFStringRef s = CFErrorCopyDescription(err); + os << "(error: " << CFErrorGetCode(err) << " description: " << fromCFString(s) << ")"; + CFRelease(s); + } + return os.str(); +} + +string +IceSSL::errorToString(OSStatus status) +{ + ostringstream os; + os << "(error: " << status; + CFStringRef s = SecCopyErrorMessageString(status, 0); + if(s) + { + os << " description: " << fromCFString(s); + CFRelease(s); + } + os << ")"; + return os.str(); +} + +std::string +IceSSL::fromCFString(CFStringRef v) +{ + string s; + if(v) + { + CFIndex size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(v), kCFStringEncodingUTF8); + IceUtil::ScopedArray<char> buffer(new char[size + 1]); + CFStringGetCString(v, buffer.get(), size + 1, kCFStringEncodingUTF8); + s.assign(buffer.get()); + } + return s; +} + +int +IceSSL::readFile(const string& file, ScopedArray<char>& buffer) +{ + IceUtilInternal::ifstream is(file); + + if(!is.good()) + { + throw CertificateReadException(__FILE__, __LINE__, "error opening file " + file); + } + + is.seekg (0, is.end); + streampos length = is.tellg(); + is.seekg (0, is.beg); + + buffer.reset(new char[length]); + is.read(buffer.get(), length); + + if(!is.good()) + { + throw CertificateReadException(__FILE__, __LINE__, "error reading file " + file); + } + return length; +} + +namespace +{ + +// +// Retrive the certificate subject key identifier, the caller must release the returned CFData +// object. +// +CFDataRef +getSubjectKeyIdentifier(SecCertificateRef cert) +{ + CFDataRef data = 0; + CFErrorRef err = 0; + CFArrayRef keys = CFArrayCreate(NULL, &kSecOIDSubjectKeyIdentifier , 1, &kCFTypeArrayCallBacks); + CFDictionaryRef values = SecCertificateCopyValues(cert, keys, &err); + CFRelease(keys); + + if(err) + { + ostringstream os; + os << "Failed to copy certificate subject key identifier\n" << errorToString(err); + CFRelease(err); + CertificateReadException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + if(values) + { + CFDictionaryRef ski = (CFDictionaryRef)CFDictionaryGetValue(values, kSecOIDSubjectKeyIdentifier); + if(ski) + { + CFArrayRef propertyValues = (CFArrayRef)CFDictionaryGetValue(ski, kSecPropertyKeyValue); + for(int i = 0, length = CFArrayGetCount(propertyValues); i < length; ++i) + { + CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(propertyValues, i); + CFStringRef label = (CFStringRef)CFDictionaryGetValue(dict, kSecPropertyKeyLabel); + if(CFEqual(label, CFSTR("Key Identifier"))) + { + data = (CFDataRef)CFDictionaryGetValue(dict, kSecPropertyKeyValue); + CFRetain(data); + break; + } + } + } + CFRelease(values); + } + return data; +} + +// +// Check the certificate basic constraints to check if the certificate is marked as a CA. +// +bool +isCA(SecCertificateRef cert) +{ + bool ca = false; + CFErrorRef err = 0; + CFArrayRef keys = CFArrayCreate(NULL, &kSecOIDBasicConstraints, 1, &kCFTypeArrayCallBacks); + CFDictionaryRef values = SecCertificateCopyValues(cert, keys, &err); + CFRelease(keys); + + if(err) + { + ostringstream os; + os << "Failed to copy certificate basic constraints\n" << errorToString(err); + CFRelease(err); + CertificateReadException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + if(values) + { + CFDictionaryRef basicConstraints = (CFDictionaryRef)CFDictionaryGetValue(values, kSecOIDBasicConstraints); + if(basicConstraints) + { + CFArrayRef propertyValues = (CFArrayRef)CFDictionaryGetValue(basicConstraints, kSecPropertyKeyValue); + int size = CFArrayGetCount(propertyValues); + for(int i = 0; i < size; ++i) + { + CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(propertyValues, i); + CFStringRef label = (CFStringRef)CFDictionaryGetValue(dict, kSecPropertyKeyLabel); + if(CFEqual(label, CFSTR("Certificate Authority"))) + { + CFStringRef value = (CFStringRef)CFDictionaryGetValue(dict, kSecPropertyKeyValue); + if(CFEqual(value, CFSTR("Yes"))) + { + ca = true; + } + break; + } + } + CFRelease(values); + } + } + return ca; +} + +// +// Search the keychain for an existing item with the same hash and type, +// the hash is the certificate subject key identifier. For private key +// items the hash should match kSecAttrApplicationLabel attribute, for +// certificate items it should match the kSecAttrSubjectKeyID attribute. +// +SecKeychainItemRef +copyMatching(SecKeychainRef keychain, CFDataRef hash, CFTypeRef type) +{ + assert(keychain); + assert(hash); + assert(type == kSecClassKey || type == kSecClassCertificate); + + const void* values[] = {keychain}; + CFArrayRef searchList = CFArrayCreate(kCFAllocatorDefault, values, 1, &kCFTypeArrayCallBacks); + + CFMutableDictionaryRef query = + CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFDictionarySetValue(query, kSecClass, type); + CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitOne); + CFDictionarySetValue(query, kSecMatchSearchList, searchList); + CFDictionarySetValue(query, type == kSecClassKey ? kSecAttrApplicationLabel : kSecAttrSubjectKeyID, hash); + CFDictionarySetValue(query, kSecReturnRef, kCFBooleanTrue); + + SecKeychainItemRef item = 0; + OSStatus err = SecItemCopyMatching(query, (CFTypeRef*)&item); + + CFRelease(searchList); + CFRelease(query); + + if(err != noErr && err != errSecItemNotFound) + { + ostringstream os; + os << "Error searching for keychain items\n" << errorToString(err); + CertificateReadException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + + return item; +} + +// +// Add an item to the keychain, if the keychain already has this item return the existing item, +// otherwise return the new added item. +// +SecKeychainItemRef +addToKeychain(SecKeychainRef keychain, SecKeychainItemRef item, CFDataRef hash, CFTypeRef type) +{ + assert(keychain); + assert(item); + assert(hash); + + SecKeychainItemRef newItem = copyMatching(keychain, hash, type); + if(!newItem) + { + CFMutableDictionaryRef query = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + CFDictionarySetValue(query, kSecUseKeychain, keychain); + CFDictionarySetValue(query, kSecClass, type); + CFDictionarySetValue(query, kSecValueRef, item); + CFDictionarySetValue(query, kSecReturnRef, kCFBooleanTrue); + + CFArrayRef added = 0; + OSStatus err = SecItemAdd(query, (CFTypeRef*)&added); + CFRelease(query); + + if(err != noErr) + { + ostringstream os; + os << "Failure adding " << (type == kSecClassKey ? "key" : "certificate") + << " to keychain\n" << errorToString(err); + CertificateReadException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + newItem = (SecKeychainItemRef)CFArrayGetValueAtIndex(added, 0); + CFRetain(newItem); + CFRelease(added); + } + + assert(newItem); + + return newItem; +} + +// +// Load keychain items (Certificates or Private Keys) from a file. On return items param contain +// the list of items, the caller must release it. +// +void +loadKeychainItems(CFArrayRef* items, CFTypeRef type, const string& file, const string& passphrase, + const PasswordPromptPtr& prompt, int passwordRetryMax) +{ + assert(type == kSecClassCertificate || type == kSecClassKey); + ScopedArray<char> buffer; + int length = readFile(file, buffer); + CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, + (const UInt8*)buffer.get(), + length, + kCFAllocatorNull); + + + SecExternalFormat format = kSecFormatUnknown; + SecExternalItemType itemType = type == kSecClassKey ? kSecItemTypePrivateKey : kSecItemTypeCertificate; + + SecItemImportExportKeyParameters params; + memset(¶ms, 0, sizeof(params)); + params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; + if(type == kSecClassKey) + { + params.flags |= kSecKeyNoAccessControl; + + // + // If the application doesn't provide an password prompt configure + // the default OS X password prompt. + // + if(!prompt) + { + params.flags |= kSecKeySecurePassphrase; + ostringstream os; + os << "Enter the password for\n" << file; + + params.alertPrompt = toCFString(os.str()); + } + } + + if(!passphrase.empty()) + { + params.passphrase = toCFString(passphrase); + } + OSStatus err = SecItemImport(data, 0, &format, &itemType, 0, ¶ms, 0, items); + if(params.passphrase) + { + CFRelease(params.passphrase); + } + + if(prompt && err == errSecPassphraseRequired) + { + for(int i = 0; i < passwordRetryMax; ++i) + { + params.passphrase = toCFString(prompt->getPassword()); + err = SecItemImport(data, 0, &format, &itemType, 0, ¶ms, 0, items); + if(params.passphrase) + { + CFRelease(params.passphrase); + } + + if(err != errSecPassphraseRequired && err != errSecInvalidData) + { + break; + } + } + } + if(params.alertPrompt) + { + CFRelease(params.alertPrompt); + } + CFRelease(data); + + if(err != noErr) + { + ostringstream os; + os << "Error reading " << (type == kSecClassCertificate ? "certificate " : "private key ") + << "from file: `" << file << "'\n" << errorToString(err); + CertificateReadException ex(__FILE__, __LINE__, os.str()); + throw ex; + } +} + +} + +// +// Helper function to generate the private key label (display name) used +// in the keychain. +// +string +IceSSL::keyLabel(SecCertificateRef cert) +{ + CFStringRef commonName; + OSStatus err = SecCertificateCopyCommonName(cert, &commonName); + if(err != noErr) + { + ostringstream os; + os << "certificate error:\n" << errorToString(err); + CertificateReadException ex(__FILE__, __LINE__, os.str()); + throw ex; + } + string label = fromCFString(commonName); + CFRelease(commonName); + return label.empty() ? "Imported Private Key" : (label + " - Private Key"); +} + +// +// Imports a certificate private key and optionally add it to a keychain. +// +void +IceSSL::loadPrivateKey(SecKeyRef* key, const string& label, CFDataRef hash, SecKeychainRef keychain, + const string& file, const string& passphrase, const PasswordPromptPtr& prompt, + int passwordRetryMax) +{ + assert(key); + CFArrayRef items = 0; + try + { + loadKeychainItems(&items, kSecClassKey, file, passphrase, prompt, passwordRetryMax); + if(items && CFArrayGetCount(items) > 0) + { + SecKeychainItemRef item = (SecKeychainItemRef)CFArrayGetValueAtIndex(items, 0); + assert(SecKeyGetTypeID() == CFGetTypeID(item)); + CFRetain(item); + *key = (SecKeyRef)item; + + CFRelease(items); + items = 0; + + if(keychain) + { + SecKeychainItemRef newItem = addToKeychain(keychain, item, hash, kSecClassKey); + assert(newItem); + CFRelease(*key); + *key = (SecKeyRef)newItem; + if(hash) + { + // + // Create the association between the private key and the certificate, + // kSecKeyLabel attribute should match the subject key identifier. + // + SecKeychainAttribute attr; + attr.tag = kSecKeyLabel; + attr.data = (void*)CFDataGetBytePtr(hash); + attr.length = CFDataGetLength(hash); + + SecKeychainAttributeList attrs; + attrs.attr = &attr; + attrs.count = 1; + + SecKeychainItemModifyAttributesAndData(newItem, &attrs, 0, 0); + } + + if(!label.empty()) + { + // + // kSecKeyPrintName attribute correspond to the keychain display + // name. + // + SecKeychainAttribute att; + att.tag = kSecKeyPrintName; + att.data = (void*)label.c_str(); + att.length = label.size(); + + SecKeychainAttributeList attrs; + attrs.attr = &att; + attrs.count = 1; + + SecKeychainItemModifyAttributesAndData(newItem, &attrs, 0, 0); + } + } + } + } + catch(...) + { + if(hash) + { + CFRelease(hash); + hash = 0; + } + + if(items) + { + CFRelease(items); + items = 0; + } + + if(*key) + { + CFRelease(*key); + *key = 0; + } + + throw; + } +} + +// +void +IceSSL::loadCertificate(SecCertificateRef* cert, CFDataRef* hash, SecKeyRef* key, SecKeychainRef keychain, + const string& file, const string& passphrase, const PasswordPromptPtr& prompt, + int passwordRetryMax) +{ + assert(cert); + CFArrayRef items = 0; + try + { + loadKeychainItems(&items, kSecClassCertificate, file, passphrase, prompt, passwordRetryMax); + if(items && CFArrayGetCount(items) > 0) + { + SecKeychainItemRef item = (SecKeychainItemRef)CFArrayGetValueAtIndex(items, 0); + assert(SecCertificateGetTypeID() == CFGetTypeID(item)); + CFRetain(item); + *cert = (SecCertificateRef)item; + + CFRelease(items); + items = 0; + + // + // Copy the public key hash, that is used when added the private key + // to create an association between the certificate and the corresponding + // private key. + // + if(hash) + { + *hash = getSubjectKeyIdentifier(*cert); + + if(keychain) + { + SecKeychainItemRef newItem = addToKeychain(keychain, item, *hash, kSecClassCertificate); + assert(newItem); + CFRelease(*cert); + *cert = (SecCertificateRef)newItem; + + } + } + + if(key) + { + loadPrivateKey(key, keyLabel(*cert), hash ? *hash : 0, keychain, file, passphrase, prompt, passwordRetryMax); + } + } + } + catch(...) + { + if(*cert) + { + CFRelease(*cert); + *cert = 0; + } + + if(*hash) + { + CFRelease(*hash); + *hash = 0; + } + + if(items) + { + CFRelease(items); + items = 0; + } + + if(key && *key) + { + CFRelease(*key); + *key = 0; + } + + throw; + } +} + +CFArrayRef +IceSSL::loadCACertificates(const string& file, const string& passphrase, const PasswordPromptPtr& prompt, + int passwordRetryMax) +{ + CFArrayRef items = 0; + loadKeychainItems(&items, kSecClassCertificate, file, passphrase, prompt, passwordRetryMax); + CFMutableArrayRef certificateAuthorities = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + if(items) + { + for(CFIndex i = 0, size = CFArrayGetCount(items); i < size; ++i) + { + SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(items, i); + if(isCA(cert)) + { + CFArrayAppendValue(certificateAuthorities, cert); + } + } + CFRelease(items); + } + return certificateAuthorities; +} + +#endif // End ICE_USE_OPENSSL + +bool +IceSSL::checkPath(string& path, const string& defaultDir, bool dir) +{ + // + // Check if file exists. If not, try prepending the default + // directory and check again. If the path exists, the string + // argument is modified and true is returned. Otherwise + // false is returned. + // + IceUtilInternal::structstat st; + int err = IceUtilInternal::stat(path, &st); + if(err == 0) + { + return dir ? S_ISDIR(st.st_mode) != 0 : S_ISREG(st.st_mode) != 0; + } + + if(!defaultDir.empty()) + { + string s = defaultDir + IceUtilInternal::separator + path; + err = ::IceUtilInternal::stat(s.c_str(), &st); + if(err == 0 && ((!dir && S_ISREG(st.st_mode)) || (dir && S_ISDIR(st.st_mode)))) + { + path = s; + return true; + } + } + + return false; +} diff --git a/cpp/src/IceSSL/Util.h b/cpp/src/IceSSL/Util.h index a49aaf60927..0224f978a86 100644 --- a/cpp/src/IceSSL/Util.h +++ b/cpp/src/IceSSL/Util.h @@ -10,20 +10,29 @@ #ifndef ICE_SSL_UTIL_H #define ICE_SSL_UTIL_H +#include <IceSSL/Config.h> #include <IceSSL/UtilF.h> #include <Ice/Network.h> #include <IceUtil/Mutex.h> #include <IceUtil/Shared.h> +#include <IceUtil/ScopedArray.h> #include <IceSSL/Plugin.h> #include <list> -#include <openssl/ssl.h> +#ifdef ICE_USE_OPENSSL +# include <openssl/ssl.h> +#else +# include <Security/Security.h> +# include <CoreFoundation/CoreFoundation.h> +#endif + +#ifdef ICE_USE_OPENSSL namespace IceSSL { -#ifndef OPENSSL_NO_DH +# ifndef OPENSSL_NO_DH class DHParams : public IceUtil::Shared, public IceUtil::Mutex { public: @@ -45,17 +54,72 @@ private: DH* _dh2048; DH* _dh4096; }; -#endif +# endif // -// Determine if a file or directory exists, with an optional default directory. +// Accumulate the OpenSSL error stack into a string. // -bool checkPath(std::string&, const std::string&, bool); +std::string getSslErrors(bool); + +} +#elif defined(ICE_USE_SECURE_TRANSPORT) + +namespace IceSSL +{ // -// Accumulate the OpenSSL error stack into a string. +// Helper functions to use by Secure Transport. // -std::string getSslErrors(bool); + +std::string fromCFString(CFStringRef); + +inline CFStringRef +toCFString(const std::string& s) +{ + return CFStringCreateWithCString(NULL, s.c_str(), kCFStringEncodingUTF8); +} + +std::string errorToString(CFErrorRef); + +std::string errorToString(OSStatus); + +// +// Read a while file into memory buffer and return the number of bytes read. +// +int readFile(const std::string&, IceUtil::ScopedArray<char>&); + + +std::string keyLabel(SecCertificateRef); + +// +// Read a private key from an file and optionaly import into a keychain. +// +void loadPrivateKey(SecKeyRef*, const std::string&, CFDataRef, SecKeychainRef, + const std::string&, const std::string&, const PasswordPromptPtr&, + int); + +// +// Read a certificate and key from an file and optionaly import then into a +// keychain. +// +void loadCertificate(SecCertificateRef*, CFDataRef*, SecKeyRef*, SecKeychainRef, + const std::string&, const std::string& = "", + const PasswordPromptPtr& = 0, int = 0); + +CFArrayRef loadCACertificates(const std::string&, const std::string& = "", const PasswordPromptPtr& = 0, + int = 0); + +} + +#endif + +namespace IceSSL +{ + +// +// Determine if a file or directory exists, with an optional default directory. +// +bool checkPath(std::string&, const std::string&, bool); } diff --git a/cpp/src/IceUtil/FileUtil.cpp b/cpp/src/IceUtil/FileUtil.cpp index 89379f5f3b6..250abf105ea 100644 --- a/cpp/src/IceUtil/FileUtil.cpp +++ b/cpp/src/IceUtil/FileUtil.cpp @@ -23,6 +23,17 @@ using namespace std; +namespace IceUtilInternal +{ +#ifdef _WIN32 +const string pathsep = ";"; +const string separator = "\\"; +#else +const string pathsep = ":"; +const string separator = "/"; +#endif +} + // // Determine if path is an absolute path // diff --git a/cpp/src/ca/iceca b/cpp/src/ca/iceca index afb9b1d77a6..0d551890688 100755 --- a/cpp/src/ca/iceca +++ b/cpp/src/ca/iceca @@ -394,13 +394,13 @@ default_ca = ice\n\ \n\ [ ice ]\n\ default_days = 1825 # How long certs are valid.\n\ -default_md = md5 # The Message Digest type.\n\ +default_md = sha256 # The Message Digest type.\n\ preserve = no # Keep passed DN ordering?\n\ \n\ [ req ]\n\ default_bits = 2048\n\ default_keyfile = $ENV::ICE_CA_HOME/ca/db/ca_key.pem\n\ -default_md = md5\n\ +default_md = sha256\n\ prompt = no\n\ distinguished_name = dn\n\ x509_extensions = extensions\n\ @@ -440,7 +440,7 @@ serial = $dir/serial # The current serial number.\n\ certs = $dir # Where issued certs are kept.\n\ RANDFILE = $dir/.rand # Private random number file.\n\ default_days = 1825 # How long certs are valid.\n\ -default_md = md5 # The Message Digest type.\n\ +default_md = sha256 # The Message Digest type.\n\ preserve = yes # Keep passed DN ordering?\n\ \n\ policy = ca_policy\n\ @@ -471,7 +471,7 @@ authorityKeyIdentifier = keyid:always,issuer:always\n\ \n\ [ req ]\n\ default_bits = 1024\n\ -default_md = md5\n\ +default_md = sha256\n\ prompt = no\n\ distinguished_name = dn\n\ x509_extensions = extensions\n\ |